字符串深入解析

之所以深入研究,是因爲在看《深入理解java虛擬機》第三版時,看到對字符串常量池的相關解析,對其中的部分不理解,所以親自試驗,畢竟實踐是檢驗真理的唯一標準

第一種情況

String str1 = new StringBuilder("計算機").append("軟件").toString();
String str3 = new StringBuilder("計算機").append("軟件").toString();

System.out.println("str1的地址:" + System.identityHashCode(str1));
System.out.println("str1.intern()的地址:" + System.identityHashCode(str1.intern()));
System.out.println("str3的內存地址:" + System.identityHashCode(str3));
System.out.println("str3.intern的內存地址:" + System.identityHashCode(str3.intern()));
運行結果:
jdk6:
str1的地址:714682869
str1.intern()的地址:798941612
str3的內存地址:1743911840
str3.intern的內存地址:798941612
jdk7:
str1的地址:1770930187
str1.intern()的地址:1770930187
str3的內存地址:2047789136
str3.intern的內存地址:1770930187
jdk8:
str1的地址:943010986
str1.intern()的地址:943010986
str3的內存地址:1807837413
str3.intern的內存地址:943010986
解析:
第一行結果解析:由於是不同對象,所以內存地址不一樣,所以返回爲false
第二行結果解析:運行str1.intern方法和str1的比較在不同jdk下出現了不同的結果
jdk6:由於jdk6的虛擬機裏面,方法區實現是永久代,常量池存在於永久代中,方法區和堆區是不同的兩個區
	 執行intern方法,若字符串常量池中沒有此字符串,會在常量池中創建一個此字符串的副本,
	 因此副本地址與堆中的地址不一樣
jdk7:由於jdk7的虛擬機,將常量池移動到了堆區中,執行intern方法,若字符串常量池中沒有此字符串,
	 會將字符串對象的引用放到常量池中,則該常量池中的地址和堆中的地址是同一個地址
jdk8:由於jdk8的虛擬機,將常量池移動到了元空間,執行intern方法,若字符串常量池中包含
	 與當前對象相當的字符串,將返回常量池中的字符串;若不存在,將對象引用放入常量池,
	 則常量池中的地址和堆中的地址爲同一個地址

第二種情況

String str4 = "計算機軟件-1";
String str5 = "計算機軟件-1";
System.out.println("str4的地址:" + System.identityHashCode(str4));
System.out.println("str4.intern()的地址:" + System.identityHashCode(str4.intern()));
System.out.println("str5的內存地址:" + System.identityHashCode(str5));
System.out.println("str5.intern()的內存地址:" + System.identityHashCode(str5.intern()));
運行結果:
jdk6:
str4的地址:1743911840
str4.intern()的地址:1743911840
str5的內存地址:1743911840
str5.intern()的內存地址:1743911840
jdk7:
str4的地址:687006504
str4.intern()的地址:687006504
str5的內存地址:687006504
str5.intern()的內存地址:687006504
jdk8:
str4的地址:2066940133
str4.intern()的地址:2066940133
str5的內存地址:2066940133
str5.intern()的內存地址:2066940133
解析:
上述情況只會在常量池中創建一個字符串,所以無論有沒有執行intern方法,地址都爲常量池中的地址

第三種情況

String str6 = new String("計算機軟件-2");
String str7 = new String("計算機軟件-2");
System.out.println("str6的地址:" + System.identityHashCode(str6));
System.out.println("str6.intern()的地址:" + System.identityHashCode(str6.intern()));
System.out.println("str7的內存地址:" + System.identityHashCode(str7));
System.out.println("str7.intern()的內存地址:" + System.identityHashCode(str7.intern()));
System.out.println(str6.hashCode() + "===" + str7.hashCode());
運行結果:
jdk6:
str6的地址:1069480624
str6.intern()的地址:322722178
str7的內存地址:1595436971
str7.intern()的內存地址:322722178
498723120===498723120
jdk7:
str6的地址:473031341
str6.intern()的地址:253286993
str7的內存地址:681902997
str7.intern()的內存地址:253286993
498723120===498723120
jdk8:
str6的地址:48612937
str6.intern()的地址:325333723
str7的內存地址:1937962514
str7.intern()的內存地址:325333723
498723120===498723120
解析:
此種情況,會在常量池中創建一個字符串,然後在堆中創建兩個對象,intern()返回的永遠是常量池中的地址,
所以str6和str7在常量池中的地址一樣,但是在堆中是不同的地址

Object的hashCode()默認是返回內存地址的,但是hashCode()可以重寫,所以hashCode()不能代表
內存地址的不同

System.identityHashCode(Object)方法可以返回對象的內存地址,不管該對象的類是否重寫了hashCode()方法

第四種情況

String str2 = new StringBuilder("ja").append("va").toString();
System.out.println("str2的內存地址:" + System.identityHashCode(str2));
System.out.println("str2.intern的內存地址:" + System.identityHashCode(str2.intern()));
運行結果:
jdk6:
str2的內存地址:1028355155
str2.intern的內存地址:616699029
jdk7:
str2的內存地址:2047789136
str2.intern的內存地址:1619081930
jdk8:
str2的內存地址:274064559
str2.intern的內存地址:1018081122
解析:
此種情況,會在常量池中創建“ja”,"va"的兩個字符串,然後toString()會產生
new String()的對象,在堆中會有一個對象,按理說執行intern方法應該和第一
種情況一致,但是事實情況卻不是,造成這種原因的問題是“java”這個字符串由於
jvm的特性,在JVM加載的時候已經將"java"字符串放入了常量池,所以執行intern
返回的是常量池中已經存在的

參考文章:
字符串常量池深入解析
String:字符串常量池
深入解析String#intern
JDK1.8版本java字符串常量池裏存的是String對象還是引用?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章