1.常量池和字符串常量池
常量池存在于字节码中,当加载字节码时,会把字节码中的常量池放入运行时常量池中,当运行到某个语句时,存在字符串常量,就会把字符串常量放入StringTable中,StringTable底层实现是hashTable,如果根据hash值判断表中是否存在这个字符串,如果存在就不会添加,否则就会加入到表中。
2.字符串拼接问题
public static void main(String[] args) {
String s1 = "ab";
String s2 = "cd";
String s3 = "abcd";
String s4 = "ab" + "cd";
String s5 = s1+s2;
System.out.println(s3 == s4); //true
System.out.println(s3 == s5); //false
System.out.println(s4 == s5); //false
}
二进制字节码:
0 ldc #2 <ab>
2 astore_1
3 ldc #3 <cd>
5 astore_2
6 ldc #4 <abcd>
8 astore_3
9 ldc #4 <abcd>
11 astore 4
13 new #5 <java/lang/StringBuilder>
16 dup
17 invokespecial #6 <java/lang/StringBuilder.<init>>
20 aload_1
21 invokevirtual #7 <java/lang/StringBuilder.append>
24 aload_2
25 invokevirtual #7 <java/lang/StringBuilder.append>
28 invokevirtual #8 <java/lang/StringBuilder.toString>
31 astore 5
局部变量表:
处理过程:
- 在执行main函数之前会把一些二进制字节码中的常量池放入运行时常量池,比如ab,cd,abcd,StringBuilder等
- 进入主函数执行第一行语句 String s1 = "ab"; ab为字面量值,会求出ab的哈希值,然后根据哈希值判断字符串常量池中是否存在这个值。如果不存在,就会把这个值加入到字符串常量池中。如果存在,则不会添加到池中,然后s1指向池中这个值。此时字符串常量池中只有[ab]
- 同样的方式执行String s2 = "cd";String s3 = "abcd"; 此时字符串常量池中有[ab,cd,abcd]
- 执行String s4 = "ab" + "cd";时,编译器会进行优化,把两个常量进行拼接成为abcd在去字符串常量池中进行比对。此时已经存在abcd,所以s4指向字符串常量池中的abcd。
- 执行String s5 = s1+s2;时,底层二进制字节码为:因为s1和s2都是对象类型,首先创建了一个StringBuilder对象,然后执行append(s1)然后执行append(s2),此时stringBuilder存储的为abcd,然后调用ToString方法,把StringBuilder转换为String,复制为s5,此时s5指向堆中的一个String对象
13 new #5 <java/lang/StringBuilder>
16 dup
17 invokespecial #6 <java/lang/StringBuilder.<init>>
20 aload_1
21 invokevirtual #7 <java/lang/StringBuilder.append>
24 aload_2
25 invokevirtual #7 <java/lang/StringBuilder.append>
28 invokevirtual #8 <java/lang/StringBuilder.toString>
3. intern方法
字符串的intern方法可以把不存在字符串常量池的字符串强制添加到字符串常量池中。
在1.8之前,调用这个方法会把这个字符串创建一个副本,通过值进行判断是否添加到字符串常量池中,如果不存在就把副本的值插入池中,如果存在就不添加,方法返回值是池中值的引用。
在1.8之后,调用这个方法会拿String对象的值进行插入,如果存在就不会插入,String对象引用不改变。如果不存在,就会把值插入到池中,然后把引用指向池中这个值,方法返回值是池中值的引用。
//JDK1.8 池中没有abcd,然后调用intern方法
public static void main(String[] args) {
String s1 = "ab";
String s2 = "cd";
String s3 = s1 + s2;
String s4 = s3.intern();
String s5 = "abcd";
System.out.println(s3 == s4); //true
System.out.println(s3 == s5); //true
System.out.println(s4 == s5); //true
}
//JDK1.8 池中有abcd,然后调用intern方法
public static void main(String[] args) {
String s1 = "ab";
String s2 = "cd";
String s5 = "abcd";
String s3 = s1 + s2;
String s4 = s3.intern();
System.out.println(s3 == s4); //false
System.out.println(s3 == s5); //false
System.out.println(s4 == s5); //true
}
4. 字符串常量池的位置
在jdk1.8之前字符串常量池存在与永久代中。
在jdk1.8之后字符串常量池存在堆中,方法区也从堆中移出,成为原空间,使用本地内存。
5. 调优
1.stringTable底层为Hahtable,通过调大hash桶的个数减少哈希碰撞,提高性能
2.intern方法,把一些重复比较多的字符串可以使用intern方法存入字符串常量池中。