首頁  >  文章  >  Java  >  深入分析Java中的intern()方法

深入分析Java中的intern()方法

Y2J
Y2J原創
2017-05-19 09:47:332338瀏覽

一、字串問題

字串在我們平時的編碼工作中其實用的非常多,而且用起來也比較簡單,所以很少有人對其做特別深入的研究。倒是面試或筆試的時候,往往會涉及比較深入和難度的問題。我在招募的時候也偶爾會問應徵者相關的問題,倒不是說一定要回答的特別正確和深入,通常問這些問題的目的有兩個,第一是考察對JAVA 基礎知識的了解程度,第二是考察應徵者對技術的態度。

我們看看以下程式會輸出什麼結果?如果你能正確的回答每一題,並且清楚其原因,那本文對你就沒什麼太大的意義。如果回答不正確或不太清楚其原理,那就仔細看看以下的分析,本文應該能幫助你清楚的理解每段程序的結果及輸出該結果的深層次原因。

程式碼段一:

package com.paddx.test.string;
public class StringTest {
    public static void main(String[] args) {
        String str1 = "string";
        String str2 = new String("string");
        String str3 = str2.intern();
 
        System.out.println(str1==str2);//#1
        System.out.println(str1==str3);//#2
    }
}

程式碼段二:

package com.paddx.test.string;
public class StringTest01 {
    public static void main(String[] args) {
        String baseStr = "baseStr";
        final String baseFinalStr = "baseStr";
 
        String str1 = "baseStr01";
        String str2 = "baseStr"+"01";
        String str3 = baseStr + "01";
        String str4 = baseFinalStr+"01";
        String str5 = new String("baseStr01").intern();
 
        System.out.println(str1 == str2);//#3
        System.out.println(str1 == str3);//#4
        System.out.println(str1 == str4);//#5
        System.out.println(str1 == str5);//#6
    }
}

程式碼片段三(1):

package com.paddx.test.string;  
public class InternTest {
    public static void main(String[] args) {
 
        String str2 = new String("str")+new String("01");
        str2.intern();
        String str1 = "str01";
        System.out.println(str2==str1);//#7
    }
}

程式碼片段三(2):

package com.paddx.test.string;
 
public class InternTest01 {
    public static void main(String[] args) {
        String str1 = "str01";
        String str2 = new String("str")+new String("01");
        str2.intern();
        System.out.println(str2 == str1);//#8
    }
}

為了方便描述,我對上述程式碼的輸出結果由#1~#8進行了編碼,下文中藍色字體部分即為結果。

二、字串深入分析

  1、程式碼段一分析

字串不屬於基本型,但是可以像基本型別一樣,直接透過字面量賦值,當然也可以透過new來產生一個字串物件。不過透過字面量賦值的方式和new的方式產生字串有本質的差異:

深入分析Java中的intern()方法

#透過字面量賦值建立字串時,會優先在常數池中查找是否已經存在相同的字串,倘若已經存在,棧中的引用直接指向該字串;倘若不存在,則在常數池中產生一個字串,再將堆疊中的參考指向該字串。而透過new的方式建立字串時,就直接在堆中產生一個字串的物件(備註,JDK 7 以後,HotSpot 已將常數池從永久代轉移到了堆中。詳細資訊可參考《JDK8記憶體模型-消失的PermGen》一文),堆疊中的引用指向該物件。對於堆疊中的字串對象,可以透過 intern() 方法來將字串新增的常數池中,並傳回指向該常數的參考。

現在我們應該可以很清楚程式碼段一的結果了:

結果#1:因為str1指向的是字串中的常數,str2是在堆中產生的對象,所以str1==str2回傳false。

結果#2:str2呼叫intern方法,會將str2中值(「string」)複製到常數池中,但是常數池中已經存在該字串(即str1指向的字串),所以直接傳回該字串的引用,因此str1==str2傳回true。

以下運行程式碼段一的程式碼的結果:

深入分析Java中的intern()方法

# 2、程式碼段二分析

對於程式碼段二的結果,還是透過反編譯StringTest01.class檔案比較容易理解:

常數池內容(部分):

深入分析Java中的intern()方法

執行指令(部分,第二列#+序數對應常數池中的項目):

深入分析Java中的intern()方法

#在解釋上述執行過程之前,先了解兩個指令:

ldc :Push item from run-time constant pool,從常數池載入指定項目的參考到堆疊。

astore_:Store reference into local variable,將引用賦值給第n個局部變數

現在我們開始解釋程式碼段二的執行過程:

0: ldc           #2:載入常數池中的第二個項目(”baseStr」)到堆疊中。

2: astore_1      :將1中的引用賦值給第一個局部變量,即String baseStr = “baseStr”;

3: ldc           #2:載入常數池中的第二項(”baseStr”)到堆疊中。

5: astore_2      :3中的引用賦值給第二個局部變量,即final String baseFinalStr=”baseStr」;

6: ldc           #3:載入常量池中的第三項(”baseStr01″)到堆疊中。

8: astore_3     :將6中的引用賦值給第三個局部變量,即String str1=”baseStr01″;

9: ldc           #3:載入常數池中的第三項(”baseStr01″)到堆疊中。

11: astore        4:將9中的引用賦值給第四局部變數:即String str2=”baseStr01″;

#結果#3:str1==str2 肯定會回傳true ,因為str1和str2都指向常數池中的相同引用位址。所以其實在JAVA 1.6之後,常數字串的「+」操作,編譯階段直接會合成一個字串。

13: new           #4:產生StringBuilder的例子。

16: dup        :複製13產生物件的參考並壓入堆疊中。

17: invokespecial #5:呼叫常數池中的第五項,即StringBuilder.方法。

以上三條指令的作用是產生一個StringBuilder的物件。

20: aload_1  :載入第一個參數的值,即」baseStr」

21: invokevirtual #6 :呼叫StringBuilder物件的append方法。

24: ldc           #7:載入常數池中的第七項(”01″)到堆疊中。

26: invokevirtual #6:呼叫StringBuilder.append方法。

29: invokevirtual #8:呼叫StringBuilder.toString方法。

32: astore        5:將29中的結果引用賦值改為第五個局部變量,即變數str3的賦值。

結果 #4:因為str3其實是stringBuilder.append()產生的結果,所以與str1不相等,結果回傳false。

34: ldc           #3:載入常數池中的第三項(”baseStr01″)到堆疊中。

36: astore        6:34中的引用賦值給第六個局部變量,即str4=”baseStr01″;

結果#5 :因為str1和str4指向的都是常數池中的第三項,所以str1==str4回傳true。這裡我們還能發現一個現象,對於final字段,編譯期直接進行了常數替換,而對於非final字段則是在運行期進行賦值處理的。

38: new           #9:建立String物件

41: dup               :複製引用並壓縮為堆疊中。

42: ldc           #3:載入常數池中的第三項(”baseStr01″)到堆疊中。

44: invokespecial #10:呼叫String.”」方法,並傳42步驟中的參考作為參數傳入此方法。

47: invokevirtual #11:呼叫String.intern方法。

從38到41的對應的原始碼就是new String(“baseStr01″).intern()。

50: astore        7:將47步驟回傳的結果賦值給變數7,也就是str5指向baseStr01在常數池中的位置。

結果 #6 :因為str5和str1都指向的都是常數池中的同一個字串,所以str1==str5回傳true。

運行程式碼段二,輸出結果如下:

深入分析Java中的intern()方法

#3、程式碼段三解析:

對於程式碼段三,在JDK 1.6 和JDK 1.7的運行結果不同。我們先來看看運行結果,然後再來解釋原因:

##JDK 1.6 下的運行結果:

深入分析Java中的intern()方法

JDK 1.7 下的運行結果:

深入分析Java中的intern()方法

根據程式碼段一的分析,應該可以很簡單地得到JDK 1.6 的結果,因為str2 和str1本來就是指向不同的位置,理應回傳false。

比較奇怪的問題在於JDK 1.7後,對於第一種情況回傳true,但調換了一下位置回傳的結果就變成了false。這個原因主要是從JDK 1.7後,HotSpot 將常量池從永久代移到了元空間,正因為如此,JDK 1.7 後的intern方法在實現上發生了比較大的改變,JDK 1.7後,intern方法還是會先去

查詢常數池中是否有已經存在,如果存在,則傳回常數池中的引用,這一點與之前沒有區別,區別在於,如果在常數池找不到對應的字串,則不會再將字串拷貝到常數池,而只是在常數池中產生一個對原始字串的參考。所以:

結果#7:在第一種情況下,因為常數池中沒有「str01」這個字串,所以會在常數池中產生一個對堆中的「str01」的引用,而在進行字面量賦值的時候,常數池中已經存在,所以直接傳回該引用即可,因此str1和str2都指向堆中的字串,傳回true。

結果#8:調換位置以後,因為在進行字面量賦值(String str1 = “str01″)的時候,常數池中不存在,所以str1指向的常數池中的位置,而str2指向的是堆中的對象,再進行intern方法時,對str1和str2已經沒有影響了,所以返回false。

【相關推薦】

1. Java免費影片教學

2. JAVA中intern()方法的使用經驗小結

3. java中intern方法的概念是什麼

#4. 分析Java中的intern()的作用

#5. 詳解String物件中的intern()

以上是深入分析Java中的intern()方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn