彻底搞懂Java 字符串常量池和intern方法

前言:

该文章是本人看了不少的博客资料总结出来的,并经过亲自验证,以下尽量用最简单的语言来解释,希望读者看完是彻底理解而不是死记硬背。
本文会在创建字符串和关于intern方法的使用来展开对字符串常量池的理解。

意义:

避免相同字符串的重复创建,减少内存消耗,提高运行效率。

字符串常量池的位置:

jdk1.6中字符串常量池存放在方法区(永久代)中
jdk1.7后中字符串常量池存放在堆中

字符串存在的位置:

一:new出来的堆内存中
二:字符串常量池中

字符串的简单创建:

代码:String str = “abcd”;
结果:尝试在字符串常量池创建一个“abdc”字符串对象返回给str,如果已存在此对象,直接返回该对象

代码:String str = new String(“abcd”);
结果:1 在堆内存new一个abcd对象返回该堆内存地址
2 尝试在字符串常量池创建一个“abcd”字符串对象,如果已存在此对象,则什么也不做

总结:双引号创建只会与常量池打交道,new出来的字符串则涉及堆内存和常量池两个位置

字符串的联合创建:

代码:String str = “ab”+“cd”;
结果:因为a和bc都是直接可见的字面量,jvm会自动把它优化为String str = “abdc”,可以视作这两个代码相等;

代码:String str = new String(“ab”)+new String(“cd”);
结果:相当于同时new两个不同的字符串ab和cd,会在堆内存创建ab对象,cd对象,abcd对象,返回堆内存的abcd对象引用给str,和在字符串常量池尝试创建ab对象和cd对象,注意!!字符串常量池没有尝试创建abcd对象。

代码:String str = new String(“ab”)+“cd”;
结果:在堆内存创建一个ab对象,abcd对象,尝试在常量池创建一个ab对象,和一个cd对象,返回堆内存的abcd给str

代码:String str = “ab”; String str1 = “bc”; String str2 = str+str1;
结果:String str2 = str+str1创建abcd这个对象到堆内存,返回堆内存的abcd给str2,无关常量池

代码:String s="ab"; String s2=s+"cd";
结果: String s2=s+"cd"在堆内存创建abcd对象,尝试在常量池创建cd,返回堆内存的abcd给s2

代码:final String s="ab"; String s2=s+"cd";
结果:被final修饰的s可以看作常量,String s2=s+“cd”;等同于String s2 = “ab”+“cd”等同于String s2 = “abcd”

intern的使用:

这个方法在jdk1.7进行了更改:

先简单回顾一下jdk1.6的使用:
当使用intern()方法时,查询字符串常量池是否存在当前字符串,若不存在则将当前字符串复制到字符串常量池中,并返回字符串常量池中的引用。

重点看jdk1.7

当使用intern()方法时,

先查询字符串常量池是否存在当前字符串,存在? 返回该常量池对象引用

不存在则再从堆中查询,存在? 在常量池保存该对象所在堆内存的引用(此时常量池便有了该常量),返回该对象的堆内存引用;

都不存在? 在字符串常量池中创建该对象,并返回字符串常量池中的引用。

从上面叙述中,可以得出其中的区别:
jdk1.6中常量池只能储存和返回常量池对象的引用

jdk1.7中常量池储存和返回常量池引用或堆内存对象引用。

练习应用:

有了上面的基础,下面直接练习一下网上比较经典的例题

String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);//false            s指向堆内存的1,s2指向字符串常量池的1,地址不同

String s3 = new String("1") + new String("1");
s3.intern();                                 在常量池创建了一个11引用指向s3
String s4 = "11";                            s4区常量池找到了11,指向s3
System.out.println(s3 == s4);//true          地址都是指向s3那个堆内存的地址

String s = new String("1");
String s2 = "1";
s.intern();
System.out.println(s == s2);//false           s指向堆内存的1,s2指向字符串常量池的1,地址不同
 
String s3 = new String("1") + new String("1");
String s4 = "11";                              在常量池创建了11对象,s4指向常量池的11
s3.intern();                                      直接在常量池找到了11对象,不再去堆内存寻找
System.out.println(s3 == s4)//false            s3指向堆内存的11,s4指向字符串常量池的11,地址不同

String str1 = new String("aa")+ new String("bb");    str1指向堆内存的aabb
System.out.println(str1.intern() == str1); //true    str1.intern()在常量池找不到aabb就去堆内存找到了aabb并在常量池创建aabb指向str1,这里相等
System.out.println(str1 == "aabb");//true            “aabb”这里去找常量池的时候找到的也是str1的那个引用,还是相等

//新加的一行代码,其余不变
String str2 = "aabb";                                 在常量池创建“aabb对象”,返回引用
String str1 = new String("aa")+ new String("bb");     str1指向堆内存的aabb
System.out.println(str1.intern() == str1); //false    这里的intern在常量池已经找到了aabb直接返回和str1指向的堆内存不一样
System.out.println(str1 == "aabb"); //false           “aabb”找到的也是在常量池中的储存的真实的“aabb”,和str1指向的堆内存也是不一样

String s="ab";
String s2=s+"cd";      s2指向堆内存的abcd
String s3="ab"+"cd";   s3在常量池创建abcd对象并指向它
System.out.println(s2==s3);//false

final String s=“ab”; 用了final是不变的常量
String s2=s+“cd”; 等同于s2 = “abcd”;
String s3=“ab”+“cd”; s3在常量池发现了abcd对象并指向它
System.out.println(s2==s3);//true

上面的注解写得比较简洁,如果看不明白,请拉到上面再重新好好理解一下上面创建字符串内存的变化和intern方法,再自行理解代码,应该是可以读懂的。

基本上如果上面的代码的输出结果自己都能清楚地解释出来,那么字符串常量池这个知识点基本就可以掌握了

感谢:
https://blog.csdn.net/shanksqian/article/details/78330294
https://blog.csdn.net/seu_calvin/article/details/52291082

猜你喜欢

转载自blog.csdn.net/qq_42862882/article/details/89179394