String.intern()方法详解

总结:

String.intern():如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。

String a = "boy";

"boy"存放在常量池中,栈中开辟名为a的空间,存放常量的内存地址;

String b = new String("boy");

在堆中创建String对象实例命名为b,"boy"存放在常量池中,

String c = new String("boy") + new String("123")

运行时,+ 相当于new,所以堆中会产生“boy 123”对象;常量池中会有”boy” “123”两个常量,而不会有”boy 123”常量;
接下来,如果使用c.intern(),那么此时:会去检查常量池是否有"boy 123",没有则添加到常量池中;

你看那么问题来了,如果大量使用intern去做检查的话,可能出现OOM,但1.8后将堆中的永久代的数据转移到了与堆不相连的本地内存空间中。

参考:Java技术——你真的了解String类的intern()方法吗

详细说明:

基本数据类型:byte,short,char,int,long,float,double,boolean,它们之间的比较应该使用(==),比较的是他们的值。
特殊的类型String:用(==)进行比较,比较的是他们在内存中的存放地址

备注:切记,==比较的是内存地址,equals比较的是值

String类型的常量池比较特殊。它的主要使用方法有两种:

直接使用双引号声明出来的String对象会直接存储在常量池中。

如果不是用双引号声明的String对象,可以使用String提供的intern方法。如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。

代码案例

第一段代码:

public static void main(String[] args) {
    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);
}

结果:
jdk6 下false false
jdk7 下false true

第二段代码:

public static void main(String[] args) {
    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);
}

打印结果为:
jdk6 下false false
jdk7 下false false

1.jdk6中的解释

jdk6中的解释

注:图中绿色线条代表 string 对象的内容指向。 黑色线条代表地址指向。

如上图所示。首先说一下 jdk6中的情况,在 jdk6中上述的所有打印都是 false 的,因为 jdk6中的常量池是放在 Perm 区(永久代)中的,Perm区和正常的Java堆是完全分开的。上面说过如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而 new 出来的 String 对象是放在Java堆中的。所以拿一个 Java堆区域的对象地址和字符串常量池的对象地址进行比较肯定是不相同的,即使调用String.intern方法也是没有任何关系的。

2.jdk7中的解释

在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm区的,Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生java.lang.OutOfMemoryError:PermGen space错误的。在 jdk7 的版本中,字符串常量池已经从Perm区移到正常的Java Heap区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称jdk8已经直接取消了Perm区域,而新建立了一个元区域。应该是jdk开发者认为Perm区域已经不适合现在 JAVA 的发展了。正式因为字符串常量池移动到JAVA Heap区域后,再来解释为什么会有上述的打印结果。

jdk1.7 常量池在堆中,jdk1.8,根据堆溢出实验结果,证明1.8字符串常量池再heap中,但是根据一些别的文章和实验结果,静态变量在heap中,运行时常量池在元空间中


jdk1.8常量池内存溢出

2.1 第一段代码解释

第一段代码解释

注:图中绿色线条代表 string 对象的内容指向。 黑色线条代表地址指向。

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);//false 
 
    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);//true
}
  • 看s和 s2 对象。String s = new String("1"); 第一句代码,生成了2个对象。常量池中的“1” 和 Java堆中的字符串对象。s.intern(); 这一句是 s 对象去常量池中寻找后发现 “1” 已经在常量池里了。
  • 接下来String s2 = "1"; 这句代码是生成一个 s2的引用指向常量池中的“1”对象。 结果就是 s 和 s2 的引用地址明显不同。

  • 看 s3和s4字符串。String s3 = new String("1") + new String("1");,这句代码中现在生成了2个最终对象,是字符串常量池中的“1” 和 JAVA Heap中的 s3引用指向的对象。中间还有2个匿名的new String("1")我们不去讨论它们。此时s3引用对象内容是”11″,但此时常量池中是没有 “11”对象的。
  • 接下来s3.intern();这一句代码,是将 s3中的"11"字符串放入String 常量池中,此时常量池中不存在"11"字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个"11"的对象,但是关键点因为 jdk7 中常量池不在永久代区域了,调整到了堆中。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向s3引用的对象。 也就是说引用地址是相同的。

个人理解:调整到了堆中,可以直接存储堆中的引用到常量池中,没有必要再生成一个“11”常量,故s3保存的还是引用。

  • 最后String s4 = "11"; 这句代码中”11″是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向s3引用对象的一个引用。所以s4引用就指向和s3一样了。因此最后的比较 s3 == s4 是 true。

2.2 第二段代码解释

第二段代码解释
public static void main(String[] args) {
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);//false 
 
    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();
    System.out.println(s3 == s4);//false
}
  • String s = newString("1"),生成了常量池中的“1” 和堆空间中的字符串对象。
  • String s2 = "1",这行代码是生成一个s2的引用指向常量池中的“1”对象,但是发现已经存在了,那么就直接指向了它。
  • s.intern(),这一行在这里就没什么实际作用了。因为"1"已经存在了。
  • 结果就是 s 和 s2 的引用地址明显不同。因此返回了false。

  • String s3 = new String("1") + newString("1"),这行代码在字符串常量池中生成“1” ,并在堆空间中生成s3引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。
  • String s4 = "11", 这一行代码会直接去生成常量池中的"11"。
  • s3.intern(),这一行在这里就没什么实际作用了。因为"11"已经存在了。

s3存的是堆中内容“11”的引用,s4存的时"11"的直接引用(内存地址)

  • 结果就是 s3 和 s4 的引用地址明显不同。因此返回了false。

Boy-20180507

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容