關於String.intern()
方法,這個問題都被問爛了,jdk1.8
之後內存模型發生了變化,內存的變化也會影響intern方法的執行,這裏有必要寫文章分析一下,請大家務必從頭開始看,這樣才能搞懂
1.字符串常量池劃分
jvm對字符串常量池在不同jkd版本有不同的劃分,這裏用hotspot
來分析,文章後部分會使用,主要有以下三種方式
大致劃分爲這幾個部分,對方法區,元空間和堆等概念模糊的朋友可以參考方法區,永久代,元空間這篇文章
2.回顧string對象在內存中的位置以及不可變性
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
字符串是不可變的,public final class String
字符串是利用其內部value字符數組
來存儲的
表示字符串的字符數組也是不可變的:private final char value[];
簡要分析一下:
str1
和str2
都是在棧上創建的字符串引用,不同的是str2
是通過new
關鍵字創建的在堆上的String
對象,str1
直接創建的是字面量abc
,str2
通過在堆上創建String
對象,而string
對象的內部屬性value數組
是一個對象引用,指向常量池中的abc
,常量池中的abc也可以理解爲是一個string
類中不可變的字符數組
3.intern()
有了上面的預備知識,我們開始分析intern()
這個方法,這裏列出網上比較熱門的代碼來分析:
public static void main(String[] args) {
String s1 = new String("1");
s1.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}
在jdk1.6中返回false,false,jdk1.7是false,true
在我們開始分析之前,我們回到第一步,先對jvm內存劃分有一個瞭解
JDK1.6以後常量池被放置在了堆空間,JDK1.8又將常量池劃分回方法區,這裏的方法區的實現是元空間
jdk1.6實現方式
通過圖片分析:
String s3 = new String("1") + newString("1")
,這行代碼在字符串常量池中生成“1” ,並在堆空間中生成s3引用指向的對象(內容爲"11")。注意此時常量池中是沒有 “11”對象的。
s3.intern()
,這一行代碼,是將 s3中的“11”字符串放入 String 常量池中,此時常量池中不存在“11”字符串,JDK1.6的做法是直接在常量池中生成一個 “11” 的對象。
但是在JDK1.7
中,常量池中不需要再存儲一份對象了,可以直接存儲堆中的引用。這份引用直接指向 s3 引用的對象,也就是說s3.intern() ==s3會返回true。
String s4 = "11"
, 這一行代碼會直接去常量池中創建,但是發現已經有這個對象了,此時也就是指向 s3 引用對象的一個引用。因此s3 == s4返回了true。
jdk1.7實現方式
總結
雖然jdk1.7以後將常量池轉移到了方法區中,但intern的工作原理卻並沒有改變,說到這裏,我們再來通過一個案例來檢驗一下你是否真的理解了intern
的作用
String s1 = new String("1");
String s2 = "1";
s1.intern();
System.out.println(s1 == s2);
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
答案是:
false
false
- s1已經在常量池中創建了"1",
s1.intern();
的作用是徒勞的,返回的是堆中的地址,而s2返回的是常量池中的地址,結果當然爲false
- s4搶先一步在常量池中創建了字面量"11",並且s3只在常量池中創建了"1",所以
s3.intern()
返回的是常量池的"11"的地址,s3當然不等於s4,返回false