最近在閱讀《深入理解Jav虛擬機器》的執行時常量池章節,看到“java語言並不要求常量池一定只有編譯器才能產生...執行期間也可以將新的常量放入常量池,這種特性被開發人員利用得比較多的時String類的intern()方法。"於是我便去深入瞭解了一下。
1 public static void main(String[] args) {
2 String a="王者";
3 String b="榮耀";
4 String c=a+b;
5 }
上述程式碼中,我通過javap命令檢視編譯期的常量池,發現只有字串"王者"和"榮耀",並無”王者榮耀“,通過反編譯得到的String c=a+b變成了,原來+號會被轉為StringBuilder,並且拼接,注意當String c="王者"+"榮耀"時,編譯器則會優化,常量池裡只有王者榮耀,並無“王者”和“榮耀”
1 String c= (new StringBuilder()).append(a).append(b).toString()
這就很有意思了,也就是說編譯時常量池中並無"王者榮耀"了,因為編譯期不會運算,只有在執行期才能執行方法得到結果。
由上圖可知,"王者榮耀"直接指向堆,並且執行時常量池無"王者榮耀"
1 public static void main(String[] args) { 2 String a="王者"; 3 String b="榮耀"; 4 String c=a+b; 5 String d=c.intern(); 6 System.out.println(d==c); 7 }
上面為什麼是true呢,接下來講述inter()
JDK1.7後,intern方法先去查詢常量池中是否有已經存在,如果存在,則返回常量池中的引用,當字串常量池中找不到對應的字串時,而只是生成一個對該字串的引用在字串常量池。所以”王者榮耀”本身不會在字串常量池,而常量池裡面儲存了在堆中王者榮耀的索引,所以為true。JDK1.7之前這裡就不說了。
前面說了intern可以在程式執行時將新的常量放入執行時常量池,接下來就開始演示intern的強大用處
public static void main(String[] args) throws Exception { long start=System.currentTimeMillis(); //獲取開始時間 String[] arr = new String[10000000]; Integer[] a= new Integer[10000000]; for (int i = 0; i < 10000000; i++) { a[i] = (int)(1+Math.random()*(3)); } for (int i = 0; i < 10000000; i++) { arr[i] = new String(String.valueOf(a[i])); } long end=System.currentTimeMillis(); //獲取結束時間 System.out.println("程式執行時間: "+(end-start)+"ms"); } //程式執行時間: 14461ms
public static void main(String[] args) throws Exception { long start=System.currentTimeMillis(); //獲取開始時間 String[] arr = new String[10000000]; Integer[] a= new Integer[10000000]; for (int i = 0; i < 10000000; i++) { a[i] = (int)(1+Math.random()*(3)); } for (int i = 0; i < 10000000; i++) { arr[i] = new String(String.valueOf(a[i])).intern(); } long end=System.currentTimeMillis(); //獲取結束時間 System.out.println("程式執行時間: "+(end-start)+"ms"); } //程式執行時間: 1688ms
很簡單的小程式,就是有無呼叫intern方法,但程式執行時間差了10倍!
我們明確的知道,會有很多重複的相同的字串產生,但是這些字串的值都是隻有在執行期才能確定的。所以,我們通過intern顯示的將其加入常量池,這樣可以減少很多字串的重複建立。
new String("王者榮耀")和new String("王者")+new String("榮耀")不管是在編譯期就確定放入常量池還是在執行期,都還是要在堆裡面建立物件的。不同就是new String("王者榮耀")還要指向常量池。new String("王者")+new String("榮耀")不需要指向常量池
new String.intern中。這個過程是不會在Java堆中再建立一個String物件的。
注意,是因為隨機數是執行期才知道的,intern把最先出現的,”1“,“2”,”3“,物件索引放入了常量池,下次在看見”1“,“2”,”3“,返回第一次在堆中String的物件地址,不用建立新物件,所以intern高效,使用intern()在堆中只會建立3個物件,而不使用intern則建立100000000個物件,並且常量池裡並沒有”1“,“2”,”3“
小問題,arri[]=new String("王者榮耀").intern()沒什麼意義的,雖然這樣寫比arri[]=new String("王者榮耀")效率高,可用程式碼驗證,arri[]=new String("王者榮耀").intern()編譯時常量池就出現“王者榮耀”,因為intern不會產生物件所以快,這裡是直接引用了常量池中的字串“王者榮耀”。但intern()並不是這樣的初衷,而是為了解決執行時才出現的常量,不是解決編譯時在字串常量池的問題,這裡比較抽象,多理解一下
筆者技術有限,如有錯誤,請指正,謝謝