String 类
String类被final关键字修饰,意味着String类不能被继承,并且它的成员方法都默认为final方法;字符串一旦创建就不能再修改。原因:String 里面有一个char[]数组实际保存着String值,,char被设置为常量,为防止char数组被更改而设置String类型不可改变
String类实现了Serializable、CharSequence、 Comparable接口
String在使用’+’号连接字符串时,java编译器会创建一个匿名StringBuilder类的append方法来实际连接字符串,toString方法用于将对象转为String类型 ,在String类中toString方法会调用valueOf()方法 所以有以下条件成立
//以下两者是等价的
s = i + ""
s = String.valueOf(i);
//以下两者也是等价的
s = "abc" + i;
s = new StringBuilder("abc").append(i).toString();
使用’+’号连接在单个是效率并不会有太大变化,但在使用大量’+’例如循环时则需要使用StringBuffer或StringBuilder
/**
* 编译期确定
* 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。
* 所以此时的"a" + s1和"a" + "b"效果是一样的。故结果为true。
*/
String s0 = "ab";
final String s1 = "b";
String s2 = "a" + s1;
System.out.println((s0 == s2)); //result = true
/**
* 编译期无法确定
* 这里面虽然将s1用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定
* 因此s0和s2指向的不是同一个对象,故上面程序的结果为false。
*/
String s0 = "ab";
final String s1 = getS1();
String s2 = "a" + s1;
System.out.println((s0 == s2)); //result = false
public String getS1() {
return "b";
}
综上,“+”连接符对于直接相加的字符串常量效率很高,因为在编译期间便确定了它的值,也就是说形如"I"+“love”+“java”; 的字符串相加,在编译期间便被优化成了"Ilovejava"。
字符串常量池
在Java的内存分配中,总共3种常量池,分别是Class常量池、运行时常量池、字符串常量池
每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性,常量池中一定不存在两个相同的字符串
String.intern 直接用双引号String对象的引用会直接存储在字符串常量池中,如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法是一个native方法,intern方法会从字符串常量池中查询当前字符串是否存在,如果存在,就直接返回当前字符串;如果不存在就会将当前字符串放入常量池中,之后再返回
在HotSpot VM中字符串常量池是通过一个StringTable类实现的,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例中只有一份,被所有的类共享;字符串常量由一个一个字符组成,放在了StringTable上。要注意的是,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。
JDK1.7的改动:
- 将String常量池 从 Perm(永久区) 区移动到了 Java Heap区(堆)
- String.intern() 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象
StringBuilder和StringBuffer StringBuilder和StringBuffer都继承自AbstractStringbuilder
这个抽象类实现AppendAble和CharSequence String 继承Serializable、CharSequence、 Comparable
主要区别
String是不可变字符序列,StringBuilder和StringBuffer是可变字符序列。执行速度StringBuilder > StringBuffer > String。
例题:
public static void main(String[] args) {
String s1 = "AB";
String s2 = new String("AB");
String s3 = "A";
String s4 = "B";
String s5 = "A" + "B";
String s6 = s3 + s4;
System.out.println(s1 == s2);
System.out.println(s1 == s5);
System.out.println(s1 == s6);
System.out.println(s1 == s6.intern());
System.out.println(s2 == s2.intern());
}
结果:false true false true false
StringBuilder是非线程安全的,StringBuffer是线程安全的
StringBuffer线程安全实现: public synchronized StringBuffer append 直接在append方法中加同步锁
解析:真正理解此题目需要清楚以下三点
1)直接使用双引号声明出来的String对象会直接存储在常量池中;
2)String对象的intern方法会得到字符串对象在常量池中对应的引用,如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用;
3) 字符串的+操作其本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象,这一点可以用javap -c命令获得class文件对应的JVM字节码指令就可以看出来
StringBuffer StringBuilder通过继承AbstractStringBuilder 获得其char[] value数组保存字符串,属性count记录char[]数组长度,就是说数组长度不够时使用Array.copy进行数组扩容.该操作损耗系统性能,所以使用StringBuffer StringBuilder类最好能确定长度进行创建capacity