探索String类下的intern()方法
参考美团技术文章:深入解析String#intern
String类的使用方式
String text1 = "text";
使用双引号声明出来的String
对象会直接存储在常量池中
String text2 = new String("text");
intern()方法概述
使用intern()方法,查询当前字符串是否存在于常量池中,不存在将当前字符串放在常量池中。
- 如果常量池中存在当前字符串,直接返回常量池中它的引用。
- 如果不存在,将当前字符串的引用放入常量池,再返回该字符串的引用
// 是一个本地方法
public native String intern();
复制代码
大体实现结构:JAVA 使用 jni 调用c++实现的
StringTable
的intern
方法,StringTable
的intern
方法跟Java中的HashMap
的实现是差不多的, 只是不能自动扩容。默认大小是1009。在jdk7中,
StringTable
的长度可以通过一个参数指定:-XX:StringTableSize=99991
字符串常量池:
字符串常量池中只存储引用,不存储内容
HotSpot VM里,记录interned string的一个全局表叫做StringTable,它本质上就是个HashSet。注意它只存储对java.lang.String实例的引用,而不存储String对象的内容
实例探究
案例1
String s1 = new String("1");
s1.intern();
String s2 = "1";
System.out.println(s1 == s2);
// jdk6 => false
// jdk7 => false
复制代码
将s1.intern();
下移一行
String s1 = new String("1");
String s2 = "1";
s1.intern();
System.out.println(s1 == s2);
// jdk6 => false
// jdk7 => false
复制代码
为什么jdk6 输出结果都是false?
jdk6中,常量池是放在Perm区的,new出来的对象是放在Heap堆区,将Heap区的对象地址和Perm区中的常量池的对象地址进行比较一定是不同的。调用intern()方法无效。
Perm 区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生
java.lang.OutOfMemoryError: PermGen space
错误的
jdk7分析:
在 jdk7 的版本中,字符串常量池已经从 Perm 区移到正常的 Java Heap 区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称 jdk8 已经直接取消了 Perm 区域,而新建立了一个元区域。应该是 jdk 开发者认为 Perm 区域已经不适合现在 JAVA 的发展了。
-
String s1 = new String("1");
产生2个对象,分别是常量池对象1->”1″,堆对象2->new String(“1”)。
-
s1.intern();
常量池中已存在”1″字符串对象,直接返回常量池中的引用,但并未使用。 -
String s2 = "1";
常量池中已经存在”1″字符串对象了,直接返回引用。 -
s1指向的是堆中的引用,s2指向的是字符串常量池的引用。所以s1==s2返回false。
s1.intern();
的次序在这里没有什么影响。
案例2
代码块1:
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
// jdk6 => false
// jdk7 => true
复制代码
将s3.intern();
下移一行
代码块2:
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
// jdk6 => false
// jdk7 => false
复制代码
代码块1:
jdk6就不做分析了。
jdk7:
-
String s3 = new String("1") + new String("1");
产生四个对象,分别是常量池对象1->”1″,对象2、对象3->new String(“1”),对象4 -> new String(“11”)。
-
s3.intern();
常量池中不存在”11″,直接将堆中”11″字符串的引用存储在字符串常量池中。 -
String s4 = "11";
常量池中已经存在”11″字符串的引用了,直接返回引用。
代码块2:
jdk6不做分析。
jdk7分析:
-
String s3 = new String("1") + new String("1");
产生四个对象,分别是常量池对象1->”1″,对象2、对象3->new String(“1”),对象4 -> new String(“11”)。
-
String s4 = "11";
常量池中不存在”11″字符串对象的。 -
s3.intern();
常量池中已存在”11″对象,返回”11″对象的引用,但并未使用。
代码改造
String s1 = new String("1");
String s2 = "1";
s1 = s1.intern();
System.out.println(s1 == s2);
// jdk7 true
String s3 = new String("1") + new String("1");
String s4 = "11";
s3 = s3.intern();
System.out.println("s3==s4:" + (s3 == s4));
// jdk7 true
复制代码
因为我们把s1的引用指向s1.intern();
返回的新引用了。