String table又称为String pool,字符串常量池,其存在于堆中(jdk1.7之后改的)。最重要的一点,String table中存储的并不是String类型的对象,存储的而是指向String对象的索引,真实对象还是存储在堆中。
此外String table还存在一个hash表的特性,里面不存在相同的两个字符串。
此外String对象调用intern()方法时,会先在String table中查找是否存在于该对象相同的字符串,若存在直接返回String table中字符串的引用,若不存在则在String table中创建一个与该对象相同的字符串。
一道经典的面试题:
public static void main(String[] args) {
String str1 = "abc";
String str2 = "ab" + "c";
String str3 = new String("ab") + "c";
String str4 = str3.intern();
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str1 == str4);
}
分析上述代码的输出情况。
答案为 true,false,true;
第一个为true的原因是jvm存在编译期优化的机制,在编译期(javac *.java时)会将可以拼接的字符串常量帮你自动拼接了,此时由于String table中已经存在了,因此会让str2指向一个与str1相同的那块地址,因此为true。
第二个为false的原因在于,由于使用了new String(),运行过程中会在堆中重新开辟一个空间存储,与之前的常量字符串没啥关系,因此为false。
第三个为true原因在于,调用intern()方法时会把在String table中查找是否存在与其值相等的(并不是地址相等)的字符串,发现里面恰好存在,因此返回该存在的引用,String table中的就是str1,因此为true。
下图为对编译后的*.class文件反编译的结果
0: ldc #2 // String abc
2: astore_1
3: ldc #2 // String abc
5: astore_2
6: new #3 // class java/lang/StringBuilder
9: dup
10: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
13: new #5 // class java/lang/String
16: dup
17: ldc #6 // String ab
19: invokespecial #7 // Method java/lang/String."<init>":(Ljava/lang/String;)V
22: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: ldc #9 // String c
27: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
33: astore_3
34: aload_3
35: invokevirtual #11 // Method java/lang/String.intern:()Ljava/lang/String;
38: astore 4
40: return
0 和 2 行为str1和str2的常量导入部分,可以看出两者的“abc”来自同一个地方,因此第一个为true,对于str3而言利用StringBuilder拼接完成后调用toString()。因此跟str1地址坑定不同。对于str4,34,35,38行,调用了本地方法intern,从这看不出具体细节。