7.1 String的不可变性
String 在jdk8及以前内部使用final char [],jdk9之后改成byte[].
通过字面量初始化String时,字符串值存储在字符串常量池。字符串常量池不允许存储相同内容得字符串值。字符串常量池底层是一个Hash Table.(数组+链表)。当数组长度过小时,会造成Hah冲突严重,影响字符串值得插入效率。使用-XX:StringTableSize可设置Hash Table得数组长度。
7.2 字符串拼接操作
常量与常量的拼接结果存在常量池,原理是编译期优化;
只要拼接对象中有一个是变量,结果就存在堆中,原理是StringBuilder;
如果对拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回对象的地址。
void test1(){
String s1 = "a"+"b"+"c";
String s2 = "abc";
System.out.println(s1 == s2);//true
}
void test2(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = s1+s2;
String s5 = s1 + "hadoop";
String s6 = s5.intern();
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//true
}
void test3(){
final String s1 = "javaEE";
final String s2 = "hadoop";
String s3 = "javaEE" + "hadoop";
String s4 = "javaEEhadoop";
System.out.println(s3 == s4);//true
}
test1:字节码文件。对于常量的拼接 在编译器就已经确定。
0 ldc #6 <abc>
2 astore_1
3 ldc #6 <abc>
5 astore_2
test2:字节码文件 字符串拼接时,含有变量时 相当于new了一个新的对象。
0 ldc #9 <javaEE>
2 astore_1
3 ldc #10 <hadoop>
5 astore_2
6 ldc #11 <javaEEhadoop> //生成s3
8 astore_3
9 new #12 <java/lang/StringBuilder> //s1 + s2 new了一个新的对象
12 dup
13 invokespecial #13 <java/lang/StringBuilder.<init>>
16 aload_1
17 invokevirtual #14 <java/lang/StringBuilder.append> //将s1 append
20 aload_2
21 invokevirtual #14 <java/lang/StringBuilder.append> //将s2 append
24 invokevirtual #15 <java/lang/StringBuilder.toString> //调用toString ===>近似于new String("")
27 astore 4 //赋值给s4
29 new #12 <java/lang/StringBuilder> //s1 + "hadoop"
32 dup
33 invokespecial #13 <java/lang/StringBuilder.<init>>
36 aload_1
37 invokevirtual #14 <java/lang/StringBuilder.append>
40 ldc #10 <hadoop>
42 invokevirtual #14 <java/lang/StringBuilder.append>
45 invokevirtual #15 <java/lang/StringBuilder.toString>
48 astore 5 //赋值给s5
50 aload 5
52 invokevirtual #16 <java/lang/String.intern>
55 astore 6
57 getstatic #7 <java/lang/System.out>
60 aload_3
61 aload 4
63 if_acmpne 70 (+7)
66 iconst_1
67 goto 71 (+4)
70 iconst_0
71 invokevirtual #8 <java/io/PrintStream.println>
74 getstatic #7 <java/lang/System.out>
77 aload_3
78 aload 5
80 if_acmpne 87 (+7)
83 iconst_1
84 goto 88 (+4)
87 iconst_0
88 invokevirtual #8 <java/io/PrintStream.println>
91 getstatic #7 <java/lang/System.out>
94 aload_3
95 aload 6
97 if_acmpne 104 (+7)
100 iconst_1
101 goto 105 (+4)
104 iconst_0
105 invokevirtual #8 <java/io/PrintStream.println>
108 return
7.3 拼接操作 与 append操作效率对比
变量拼接时,需大体经过3个步骤
1 创建StringBuilder对象
2 执行append操作
3 调用toString() 返回给新的变量
所以创建StringBuilder对象 执行append操作效率更高。同时 可以执行StringBuilder的有参构造,执行byte数组的大小 防止不断扩容。
StringBuffer、StringBuilder为字符序列可变的字符串,当一个StringBuffer被创建以后,通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象,一个不可变的对象。
7.4 intern
调用intern()方法后, 会去字符串常量池中调用equals方法判断是否含有当前字符串值,如果没有则创建后返回其地址;如果已经有了则直接返回地址。
如何保证变量s指向字符串常量池。、
1 使用字面量赋值 String s = "123";
2 调用intern方法 String s = new String("123").intern();
7.5 new String()到底创建了几个对象?
String a = new String("AB");
两个对象 字符串常量池中AB 栈中a
String a = new String("A") + new String("B");
六个对象: 常量池中A B
字符串拼接 StringBuilder 对象
new String(A) new String("B")
StringBuilder.toString()返回的对象 a
7.6 intern面试题
void test3(){
String s1 = new String("1"); //在常量池中创建一个"1" 然后创建一个String对象 将String对象的地址赋值给s1
s1.intern(); //常量池中已创建 此代码没什么作用
String s2 = "1"; //s2 指向 常量池 "1"
System.out.println(s1 == s2); //false s1指向String对象 s2指向字符串常量池
String s5 = new String("2").intern(); //在常量池中创建一个"2" s5指向常量池中2的地址
String s6 = "2"; //s2 指向 常量池 "2"
System.out.println(s5 == s6); //true
String s3 = new String("1") + new String("1"); //s3 指向 new StringBuilder("11").toString()返回的对象地址 此时常量池中不存在常量11
s3.intern(); //创建常量11
String s4 = "11"; //s4 指向字符串常量池中的11
System.out.println(s3 == s4);//jdk6 false jdk7/8 true
// jdk6中 把此对象复制一份 并返回串中的对象地址
//jdk6中 把此对象引用地址复制一份 并返回 池中存的是s3对象的引用地址 s3 s4指向同一对象
String s7 = new String("3") + new String("3"); //s7 指向 new StringBuilder("33").toString()返回的对象地址 此时常量池中不存在常量33
String s8 = "33"; //s8 指向字符串常量池中的33
String s9 = s7.intern();
System.out.println(s7 == s8);// false
System.out.println(s9 == s8);// true
}
对于存在大量的重复的字符串时 使用intern方法 可大幅降低内存。