在使用java编程过程中我们最常使用的类无非就是String ,下面我们详细了解一下String在JDK中的实现以及分析
目录
String类
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
String是一个final类 实现了Serializable、Comparable、CharSequence接口,String一旦创建便不可修改其指针指向内存的值,
大家或许说,我使用String不是经常在修改吗?那我看看修改String的简单过程 如Hello -> HelloWorld
其实只是引用的指向变为了另一个内存空间,并且新的内存空间的内容为修改后的值,在这里我们有疑问为什么会设计成为final类 即该类具有不可变性,不可变性会带来一定的效率问题,如使用 " + " 操作连接字符串时,会不断创建新的对象,如果存在大量拼接的情况这样会产生大量的垃圾对象,导致频繁GC,影响了整体服务性能
但是编译器可以让不可变的字符串共享,即如果String的内容一致,只需要复制多个引用即可实现共享,而不需要每次使用都创建一个新的对象,Java的设计者认为字符串共享带来的高效率远远胜过提取、拼接字符串所带来的低效率,此外,如果指定一个类为final,则该类所有的方法都是final。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能大大提高,当然在线程安全性上也有相应的保证。
String对象的创建方式
首先看一下常量池的概念
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。
方式一 这种创建的方式是比较推荐的一种方式
首先会从字符串常量池中查找是否有该字符串,如果存在则将该指针复制一份给引用并返回,不存在则创建
这也是String为final类的一个好处,可以在常量池中共享给使用者
String str = "Hello";
方式二 不推荐
首先在java堆区创建String对象
然后从字符串常量池中查找是否有该字符串,如果不存在则创建一份
String str = new String("Hello")
简单看一下这两种方式的区别
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
System.out.println(str1 == str2);//true
System.out.println(str1 == str3);//false
String str4 = "HelloWorld";
String str5 = "Hello" + "World";
String str6 = str1 + "World";
//因为字符串常量在编译时确定 所以Str5编译后为字符串常量"HelloWorld"
System.out.println(str4 == str5);//true
//而Str1为变量,编译时不能确定,
System.out.println(str4 == str6);//false
使用new构造器创建字符串对象一定会开辟一个新的heap空间
而双引号则是采用了String interning(字符串驻留)进行了优化,效率比构造器高。
String类分析
- 属性
/** 该数组用于存储字符串 */
private final char value[];
/** 当前String对象的hash值 */
private int hash; // Default to 0
String内部维护的是一个char数组,数组用于存储字符,String的基本所有方法都是围绕这个数组进行,可以看做是String的核心,hash用于缓存当前String对象hash值
- 方法
String重写了Object的equals方法,同一个String对象返回true或者String的内容相等(即value数组的内容是否一致)也返回true
//String 重写了 Object的equals方法
public boolean equals(Object anObject) {
//首先判断对象是否相等 如果对象相等直接返回true
if (this == anObject) {
return true;
}
//然后判断对比对象是否为String类型,如果是,
//则判断String内部char数组的元素是否完全一致,即字符串内容是否相同,
//如果相同则返回true,否则返回false
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
String的hashcode实现
//计算公式为s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
hashcode到底有什么作用,还是直接看Object类的hashcode方法注释,大致意思是 在两个对象使用equal方法比较之后为true则hashcode的值也必须一致,hashcode可以为提升hash表的性能 (如hashmap)
StringBuffer、StringBuilder
String也存在局限性,也就是不可变性带来的性能问题,但JDK提供StringBuffer和StringBuilder来弥补String的不足,当我们有需求做大量字符串拼接时,使用String会带来很大的性能开销,这时候使用StringBuffer或者StringBuilder可以高性能的完成,StringBuffer、StringBuilder其内部同样使用value数组存储字符数据,和String的不同之处,它们不需要重复创建对象,只需要对内部value数组进行扩容来满足存储的需要,StringBuffer和StringBuilder唯一不同的是StringBuffer是线程安全的类,可以在多线程环境下不用考虑线程同步的问题,而StringBuilder则性能更好,它们完成的功能是一致的。
总结
String的特点就是其不可变性、简单,在多线程环境中不用考虑线程安全问题,缺点是频繁的变更String的值会带来内存的开销,如有这种经常变化的情况,使用StringBuilder或者StringBuffer这样可以更高性能并且节省内存资源占用。