前言:
该文章是本人看了不少的博客资料总结出来的,并经过亲自验证,以下尽量用最简单的语言来解释,希望读者看完是彻底理解而不是死记硬背。
本文会在创建字符串和关于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方法,再自行理解代码,应该是可以读懂的。
基本上如果上面的代码的输出结果自己都能清楚地解释出来,那么字符串常量池这个知识点基本就可以掌握了!
|
|