理解String.intern()和String类常量池疑难解析例子
发布日期:2021-06-29 15:35:58
浏览次数:2
分类:技术文章
本文共 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
第一次来,支持一个
[***.219.124.196]2024年04月02日 23时19分45秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
禁止显示系统隐藏文件方法
2019-04-29
定义可变参数的宏
2019-04-29
Win10系统如何取消锁屏
2019-04-29
Diskspd.exe工具测试文件读写性能的一些经验
2019-04-29
windows c/c++客户端程序通过cJSON解析json格式配置文件参考代码
2019-04-29
windows驱动送测WHQL时inf文件注意事项
2019-04-29
【转载】分析Windows的死亡蓝屏(BSOD)机制
2019-04-29
FsRtlIsNameInExpression使用示例
2019-04-29
文件系统驱动里获取读写当前文件的进程信息
2019-04-29
NtCreatePagingFile 函数源码
2019-04-29
Linux下删除硬盘上的分区
2019-04-29
CString,int,string,char*之间的转换【收藏】
2019-04-29
NtFsControlFile
2019-04-29
移动磁盘文件,用NtFsControlFile还是用DeviceIoControl
2019-04-29
NtDeviceIoControlFile
2019-04-29
INF文件详解
2019-04-29
inf文件详解(转)
2019-04-29
在下拉列表框中显示树状结构
2019-04-29
驱动对象与设备对象
2019-04-29