String的intern()方法详解
1、先看一个例子
String str1 = new String("A")+ new String("B");
System.out.println(str1 == str1.intern());
System.out.println(str1 == "AB");
JDK1.8的输出结果:
true
true
上边例子加一行代码其余不变:
String str2 = "AB";
String str1 = new String("A")+ new String("B");
System.out.println(str1 == str1.intern());
System.out.println(str1 == "AB");
JDK1.8的输出结果:
false
false
只是定义了一个str2,应该和str1没有什么关系吧,但是为什么会影响到最终的结果呢?
这时候就需要我们深入了解intern()
方法了!!!
2、深入了解intern()方法
JDK1.6以及以前版本中,常量池是放在 Perm 区(属于方法区)中的,熟悉JVM的话应该知道这是和堆区完全分开的。JDK1.7后,常量池被放入到堆空间中,这导致intern()函数的功能不同,具体怎么个不同法,且看看下面例子,分析图是直接粘贴过来的:
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
输出结果
JDK1.6以及以下:false false
JDK1.7以及以上:false true
再分别调整上面代码2.3行、7.8行的顺序:
String s = new String("1");
String s2 = "1";
s.intern();
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
输出结果:
JDK1.6以及以下:false false
JDK1.7以及以上:false false
2.1、JDK1.6分析
JDK1.6及以下版本intern()的作用:检查常量池里是否存在这个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会把这个字符串添加到常量池中,然后再返回它的引用。
先看第一段代码:
String s = new String("1")
会在常量池中创建常量“1”,堆中创建一个String对象指向常量池的“1”,栈中的s指向堆中的String对象s.intern()
会返回常量池中的地址,在这里没有什么意义String s2 = "1"
s2指向常量池中的“1”System.out.println(s == s2)
常量池中地址和堆中的地址是不一样的所以为falseString s3 = new String("1") + new String("1")
其中new String("1")
创建和第一行s创建是一样的。字符串的拼接用到了StringBuilder,这里不多做介绍。最终会新创建一个存放“11”的String对象(注意这个时候的“11”是在堆内存中),s3指向堆中的String对象s3.intern()
去常量池查找“11”发现不存在,将“11”复制到常量池中String s4 = "11"
s4会指向常量池中的“11”System.out.println(s3 == s4)
常量池中地址和堆中的地址是不一样的所以为false
第二段代码和第一段基本相同,只有s3.intern()
由于在String s4 = "11"
之后执行,所以会在常量池找到“11”,所以不会进行复制
2.1、JDK1.7+分析
JDK1.7以及以上的版本intern()的作用:检查常量池里是否存在这个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会把这个字符串在堆内存中的地址添加到常量池中,然后再返回这个地址。
先看第一段代码的情况:
String s = new String("1")
会在常量池中创建常量“1”,堆中创建一个String对象指向常量池的“1”,栈中的s指向堆中的String对象s.intern()
会返回常量池中的地址,在这里没有什么意义String s2 = "1"
s2指向常量池中的“1”System.out.println(s == s2)
常量池中地址和堆中的地址是不一样的所以为falseString s3 = new String("1") + new String("1")
其中new String("1")
创建和第一行s创建是一样的。字符串的拼接用到了StringBuilder,这里不多做介绍。最终会新创建一个存放“11”的String对象(注意这个时候的“11”是在堆内存中),s3指向堆中的String对象s3.intern()
去常量池查找“11”发现不存在,将“11”在堆内存中的地址放到常量池中String s4 = "11"
"11"在常量池中存在,所以s4会指向常量池中“11”对应的引用地址(也就是s3的地址)System.out.println(s3 == s4)
s3和s4中的地址是一样的所以为true
再看第二段代码:
String s = new String("1")
会在常量池中创建常量“1”,堆中创建一个String对象指向常量池的“1”,栈中的s指向堆中的String对象String s2 = "1"
s2指向常量池中的“1”s.intern()
会返回常量池中的地址,在这里没有什么意义System.out.println(s == s2)
常量池中地址和堆中的地址是不一样的所以为falseString s3 = new String("1") + new String("1")
其中new String("1")
创建和第一行s创建是一样的。字符串的拼接用到了StringBuilder,这里不多做介绍。最终会新创建一个存放“11”的String对象(注意这个时候的“11”是在堆内存中),s3指向堆中的String对象String s4 = "11"
在常量池创建“11”,s4指向常量池s3.intern()
由于“11”已经存在,会返回常量池中的地址,在这里没有什么意义System.out.println(s3 == s4)
常量池中地址和堆中的地址是不一样的所以为false
3、总结
看完这些再看开篇时候的例子就很清楚了吧。最后总结以下:
intern()的作用:检查常量池里是否存在这个字符串,如果存在,就返回池里的字符串;如果不存在,JDK1.6及以下版本会把这个字符串添加到常量池中,JDK1.7以及以上的版本则把这个字符串在堆内存中的地址添加到常量池中。