標籤:.net sys const 運行 對象 ons als java 並且
public class RuntimeConstantPoolOOM { public static void main(String[] args) { String str1 = new StringBuilder("電腦").append("軟體").toString(); // String str3= new StringBuilder("電腦軟體").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder("Java(TM) SE ").append("Runtime Environment").toString(); System.out.println(str2.intern() == str2); }
書中寫道,如果JDK1.6會返回兩個false,JDK1.7運行則會返回一個true一個false。
因為JDK1.6中,intern()方法會把首次遇到的字串執行個體複製到永久代中,返回的也是永久代中這個字串的執行個體的引用,而StringBulder建立的字串執行個體在Java堆上,所以必然不是同一個引用,將返回false。
在JDK1.7中,intern()的實現不會在複製執行個體,只是在常量池中記錄首次出現的執行個體引用,因此返回的是引用和由StringBuilder.toString()建立的那個字串執行個體是同一個。
str2的比較返回false因為"java"這個字串在執行StringBuilder.toString()之前已經出現過,字串常量池中已經有它的引用了,不符合“首次出現”的原則,而“電腦軟體”這個字串是首次出現,因此返回true。
一、
那麼就有疑問了,這個“java”字串在哪裡出現過呢?顯然並不是直接出現在這個類裡面。
我們分別開啟String 、StringBuilder和System類的源碼看看有啥發現,
其中在System類裡發現 參考72753494
二、
這個問題解決了,然後我又發現了另外一個問題。除了這些在虛擬機器載入時就初始化的常量,定義其他的字串常量,比如“nihao”.
先運行這個代碼String str3 = new StringBuilder("ni").append("hao").toString();System.out.println(str3==str3.intern());通過上面的解釋,運行結果為true.在運行這個代碼String str3 = new StringBuilder("nihao").toString();System.out.println(str3==str3.intern());其結果是什嗎?應該還是true吧,畢竟通過上一個運行結果可以知道"nihao"這個字串常量沒有被積極式載入到常量池中。但是運行結果卻是false.
StringBuilder的append方法沒有改變字串的引用地址,只是把其值改變了,為什麼加了append返回的是true,沒有加append卻是false呢?如果在後面多加幾個append返回的也是true。
解決:
先運行這個代碼
String str3 = new StringBuilder("ni").append("hao").toString();
System.out.println(str3==str3.intern());
上面的代碼等價於下面的代碼
String a = "ni";
String b = "hao";
String str3 = new StringBuilder(a).append(b).toString();
System.out.println(str3==str3.intern());
很容易分析出:
“nihao” 最先建立在堆中 str3.intern()然後緩衝在字串常連池中 運行結果為true.
lz代碼
String str3 = new StringBuilder("nihao").toString();
System.out.println(str3==str3.intern());
可以寫成下面的形式
String a = "nihao";
String str3 = new StringBuilder(a).toString();
System.out.println(str3==str3.intern());
很容易分析出:
“nihao” 最先建立在常量池中, 運行結果為false.
三、關於java intern的一些深入理解
儘管在輸出中調用intern方法並沒有什麼效果,但是實際上後台這個方法會做一系列的動作和操作。在調用”ab”.intern()方法的時候會返回”ab”,但是這個方法會首先檢查字串池中是否有”ab”這個字串,如果存在則返回這個字串的引用,否則就將這個字串添加到字串池中,然會返回這個字串的引用。
可以看下面一個範例:
String str1 = "a";String str2 = "b";String str3 = "ab";String str4 = str1 + str2;String str5 = new String("ab"); System.out.println(str5.equals(str3));System.out.println(str5 == str3);System.out.println(str5.intern() == str3);System.out.println(str5.intern() == str4);
得到的結果:
truefalsetruefalse
為什麼會得到這樣的一個結果呢?我們一步一步的分析。
- 第一、str5.equals(str3)這個結果為true,不用太多的解釋,因為字串的值的內容相同。
- 第二、str5 == str3對比的是引用的地址是否相同,由於str5採用new String方式定義的,所以地址引用一定不相等。所以結果為false。
- 第三、當str5調用intern的時候,會檢查字串池中是否含有該字串。由於之前定義的str3已經進入字串池中,所以會得到相同的引用。
- 第四,當str4 = str1 + str2後,str4的值也為”ab”,但是為什麼這個結果會是false呢?先看下面代碼:
String a = new String("ab");String b = new String("ab");String c = "ab";String d = "a" + "b";String e = "b";String f = "a" + e;System.out.println(b.intern() == a);System.out.println(b.intern() == c);System.out.println(b.intern() == d);System.out.println(b.intern() == f);System.out.println(b.intern() == a.intern());
運行結果:
falsetruetruefalsetrue
由運行結果可以看出來,b.intern() == a和b.intern() == c可知,採用new 建立的字串對象不進入字串池,並且通過b.intern() == d和b.intern() == f可知,字串相加的時候,都是靜態字串的結果會添加到字串池,如果其中含有變數(如f中的e)則不會進入字串池中。但是字串一旦進入字串池中,就會先尋找池中有無此對象。如果有此對象,則讓對象引用指向此對象。如果無此對象,則先建立此對象,再讓對象引用指向此對象。
當研究到這個地方的時候,突然想起來經常遇到的一個比較經典的Java問題,就是對比equal和==的區別,當時記得老師只是說“==”判斷的是“地址”,但是並沒說清楚什麼時候會有地址相等的情況。現在看來,在定義變數的時候賦值,如果賦值的是靜態字串,就會執行進入字串池的操作,如果池中含有該字串,則返回引用。
執行下面的代碼:
String a = "abc";String b = "abc";String c = "a" + "b" + "c";String d = "a" + "bc";String e = "ab" + "c"; System.out.println(a == b);System.out.println(a == c);System.out.println(a == d);System.out.println(a == e);System.out.println(c == d);System.out.println(c == e);
啟動並執行結果:
truetruetruetruetruetrue
《深入理解java虛擬機器》String.intern()探究