深入理解字符串的底层存储方式
引言
以下讨论的,包括图示,都是基于JDK1.8以上。因为JDK1.7的常量池在方法区,而不是在Java堆中
先了解字符串常量在内存的表示方式,接着了解字符串对象在内存的表示方式。在了解两种字符串表现方式后,String.intern()就将会很容易理解。
关于Java堆栈内容可以阅读我的《深入理解Java虚拟机(二) — JVM内存管理》进行了解。
Case1:字符串常量
这是一张字符串常量在内存中表示的图片。由图可知,字符串常量都会被放进常量池中。‘
字符串常量创建
在常量池中查找是否有已经有存在的相同字符串
-
若有,则将变量引用指向该字符串(eg:图中的str2)
-
若没有,则在常量池中创建创建字符串,并将引用指向它(eg:图中的str3)
代码模拟
void Example(){
String str1 = "abc";
String str2 = "abc";
String str3 = "sss";
System.out.println(str1 == str2);
System.out.println(str1 == str3);
}
结果: true false
因为str1与str2的引用是指向同一个地方,而str1与str3却指向不同的地址
Java中的字符串比较
通过String.equals(str),比较的是字符串中的值是否相等
通过 == ,比较的是字符变量所引用的地址是否相同
tips:这也是比较广义的说法,并非是精确的描述
Case2:字符串对象
这是一张字符串对象在内存中表示的图片。由图可知,字符串对象都是作为实例放在Java堆中,而不是常量池。
字符串对象创建
在Java堆中实例化一个String对象,返回变量一个相应的地址。
不同的对象,即使字符串值相同,引用也是不相同的。
代码模拟
void Example(){
String str1 = new String(ab);
String str2 = new String(ab);
System.out.println(str1.equals(str2)); //值相同
System.out.println(str1 == str2); //引用地址不同
}
结果: true false
String.intern()
方法目的: 该方法实现从常量池中取回字符串对象的值。
看完这个,可能有疑问:为什么字符串对象能从常量池中返回值?!
其实,在调用intern的时候,它进行了以下的步骤:
检查常量池中,有没有已存在的相同字符串
- 若有,返回常量池的该地址
- 若没有,在常量池创建一个索引指向原实例地址,并返回该地址指向的字符串(eg: str1)
代码模拟
void Example(){
String str1 = new String(ab);
String str2 = new String(ab);
System.out.println(str1.equals(str2)); //值相同
System.out.println(str1 == str2); //引用地址不同
System.out.println(str1.intern() == str2.intern()); //地址相同,都是0xf0f010
}
结果 true true false
解析
-
左边的
str1.intern()
在常量池创建了一个str1的索引,返回str1的字符串 -
右边的
str2.intern()
在常量池中找到str1的索引,发现值相同,所以也返回了str1的字符串
Tips: 在JDK1.7以前,调用intern时,若常量池没有已存在字符串时,是创建一个原字符串的副本,而不是索引。