理解String.intern()和String类常量池疑难解析例子
发布日期:2021-06-29 15:35:58 浏览次数:3 分类:技术文章

本文共 5306 字,大约阅读时间需要 17 分钟。

理解String.intern()和String类常量池疑难解析例子

一、Java内存模型

在理解之前先了解下Java的内存模型。

二、string类创建对象

String对象的创建通常有两种方式:

String str1 = “abcd”;

String str2 = new String(“abcd”);

一种是通过字符串常量池的方式;

一种是通过new在堆中开启空间,创建新的对象;

三、String.intern()

String.intern()是一个native本地方法,它的作用是:

  • 如果运行时常量池中已经包含了一个等于此string对象内容的 字符串,那么直接返回常量池中该字符串的引用;

如果没有,那么

  • 在jdk1.6中,会将此string对象放入常量池,然后返回这个string对象的引用;(此时引用的串在常量池)
  • 在jdk1.7中,放入一个引用,指向堆中的string对象的地址,返回这个引用地址(此时引用的串在堆中)

四、String常见的面试例子

例子一

String h = new String("cc");        String intern = h.intern();        System.out.println(intern == h); // 返回false

这里为什么不返回true,而是返回false呢?

解释:

new String(“cc”) 后,堆中创建了"cc",“cc"也会缓存到常量池,可以认为占用了2个字符串对象内存(因为你创建了一个“cc”字符串对象,但是放到了2个地方占用了2块内存)!当你String intern = h.intern();其中h.intern()会去常量池检查是否有了"cc”,结果发现有了,那么此时返回常量池的引用地址给intern,用常量池的引用intern和堆中的h引用去比较肯定不相等。所以返回false。

例子二

String str2 = new String("str") + new String("01");String str1 = "str01";str2.intern();System.out.println(str2 == str1); // false

解释:

第一句new String("str") + new String("01");现在在堆中创建了"str",同时"str"缓存到常量池,创建了"01",同时"01"也缓存到常量池,再进行连接,堆中出现了"str01"。此时常量池中有:"str","01",此时堆中有"str","01","str01"。str2引用指向堆中的"str01"。   接着第二句String str1 = "str01"; 发现常量池没有"str01",那么直接在常量池创建"str01"。此时常量池中有:"str","01","str01",此时堆中有"str","01","str01"。str1指向常量池中的"str01"。   接着第三句str2.intern();检查常量池是否有"str01",结果发现有了,返回常量池"str01"的地址,很可惜,没有变量去接收,所以这一句没什么用,str2指向也不会改变,还是指向堆中"str01"。    第四句去打印str2==str1,一个堆中的"str01"地址和一个常量池中的"str01"地址比较,返回false。

例子三

String str2 = new String("str") + new String("01");    String str1 = "str01";    String str3 = str2.intern();    System.out.println(str3 == str1); // true

解释:

比问题二多了一个str3引用保存了常量池"str01",str3和str1均指向常量池的"str01",所以返回true

例子四

String str2 = new String("str") + new String("01");    str2.intern();    String str1 = "str01";    System.out.println(str2 == str1);     String str3 = new String("str01");    str3.intern();    String str4 = "str01";    System.out.println(str3 == str4);

解释:

第一句new String("str") + new String("01");现在在堆中创建了"str",同时"str"缓存到常量池,创建了"01",同时"01"缓存到常量池,再进行连接,堆中出现了"str01"。此时常量池中有:"str","01",此时堆中有"str","01","str01"。str2引用指向堆中的"str01"。    第二句,str2.intern();检查到常量池不存在"str01",如果在jdk1.6,那么就将堆中的"str01"添加到常量池中,如果是jdk1.7,那么就在常量池保存指向堆中"str01"的地址,即保存堆中"str01"的引用。接下来的讲解以jdk1.7为准!!所以这里是在常量池保存了堆中"str01"的引用。   第三句,String str1 = "str01";检查到常量池有一个引用保存了这个串,str1就直接指向这个地址,即还是堆中的"str01"。   第四句,str2==str1是否相等,str2指向堆中的"str01",str1指向常量池的某个地址,这个地址恰好是指向堆中的"str01",所以仍然是true。   第五句,String str3 = new String("str01");又在堆中创建了"str01",现在堆中有了2个"str01",而常量池已经有"str01"引用,不再缓存进去。(结论是常量池有equals相同的串或者引用指向equals相同的串就不再缓存)   第六句,str3.intern(); 去检查一下常量池到底有没有"str01"呢?检查发现常量池有个引用指向堆中的"str01",JVM认为常量池是有"str01"的,那么直接返回指向堆中的"str01"地址,很可惜,没有变量去接收,这一句在这里没有什么用。   第七句,String str4 = "str01";检查到常量池有个引用指向堆中的"str01",那么str4也保存这个引用,所以这个"str01"还是堆中的第一个"str01"。   第八句,打印str3==str4,str3是堆中新建的第二个"str01",str4保存引用指向第一个堆中的"str01",两块堆的地址,所以返回false。

例子五

String str2 = new String(“str”) + new String(“01”);

为什么不String str2 = new String(“str01”);呢? 区别在哪里呢?

解释:

我们来单独执行比较,前者new String("str")堆中创建"str",同时"str"缓存到常量池,new String("01")在堆中创建"01",同时"01"缓存到常量池,相加操作只会在堆中创建"str01",所以前者执行以后,内存:堆中有"str","01","str01",常量池中"str","01"。str2引用指向堆中的"str01"。   现在来看后者String str2 = new String("str01");这个就是在堆中创建"str01"同时"str01"缓存到常量池,str2引用指向堆中的"str01",内存:堆中有"str01",常量池中有"str01"。   综上所述,区别就在于这些串处于不同的位置,前者在常量池是没有"str01"的。

例子六

String s = new String("abc");     String s1 = "abc";     String s2 = new String("abc");     System.out.println(s == s1);// 堆内存"abc"和常量池"abc"相比,false    System.out.println(s == s2);// 堆内存s和堆内存s2相比,false    System.out.println(s == s1.intern());// 堆内存"abc"和常量池"abc"相比,false    System.out.println(s == s2.intern());// 堆内存"abc"和常量池"abc"相比,false    System.out.println(s1 == s2.intern());// 常量池"abc"和常量池"abc"相比,true    System.out.println(s.intern() == s2.intern());// 常量池"abc"和常量池"abc"相比,true

例子七

String s1 = "abc";     String s2 = "a";     String s3 = "bc";     String s4 = s2 + s3;     System.out.println(s1 == s4);//false,因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。    // s1指向常量池"abc",s4指向堆中"abc"(append连接而来)    String S1 = "abc";     final String S2 = "a";     final String S3 = "bc";     String S4 = S2 + S3;     System.out.println(S1 == S4);//true,因为final变量在编译后会直接替换成对应的值    // 所以实际上等于s4="a"+"bc",而这种情况下,编译器会直接合并为s4="abc",所以最终s1==s4为true。

例子八

String str1 = "abcd"; // 常量池创建"abcd"    String str2 = "abcd"; // str2还是上一步的"abcd"    String str3 = "ab" + "cd"; // 常量池创建"ab"和"cd",连接过程编译器直接优化成"abcd",而常量池已经有了"abcd",所以str3和str1都指向"abcd"    String str4 = "ab"; // 常量池已经有了"ab"    str4 += "cd"; // str4+"cd"连接的字符串编译器不能优化,所以此时str4指向堆中的"abcd"    // 因为"ab"是str4引用的,如果是两个变量s1="ab", s2="cd",s1+s2连接,那么只有用final修饰的指向"ab"的s1和final修饰的指向"cd"的s2相连接才能优化成"abcd"    // 如果只有一个变量s1和常量池的常量连接s1+"cd",这个变量s1也需要final修饰才会优化成"abcd"    System.out.println(str1 == str2); // true    System.out.println(str1 == str3); // true    System.out.println(str1 == str4); // false    System.out.println("================");    String s1 = "a";    String s2 = "b";    String s3 = "ab";    final String ss1 = "a";    final String ss2 = "b";    System.out.println(s1 + s2 == s3); // false, 有变量引用的字符串是不能优化的,除非变量是final修饰或者直接"a"+"b"的常量形式,这一行就是s1+s2生成堆里的"ab"和常量池的"ab"在比较    System.out.println(ss1 + ss2 == s3); // true,原因见上一行,原理和下一行相同,都是常量连接    System.out.println("a" + "b" == s3); // true,常量池的"a"和"b"连接,根据Copy On Write机制, 副本连接生成"ab",发现已存在,直接指向"ab",所以和s3相等

转载地址:https://codingchaozhang.blog.csdn.net/article/details/114923140 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:python flask打造前后端分离的口罩检测
下一篇:【大话Mysql面试】-Mysql如何恢复数据?如何进行主从复制?Binlog日志到底是什么?

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2024年04月24日 01时37分57秒