1. 程式人生 > >徹底理解 String.intern

徹底理解 String.intern

然後可對 String.intern 有一個大體地認識。
本篇中的 Java 例子使用的是 jdk1.8。

Java 中的常量池分靜態常量池和執行時常量池。

靜態常量池

編譯後的 *.class 檔案中除了有類、方法等描述資訊以外,還有常量池,主要存放兩大類常量:字面量(Literal)和符合引用量(Symbolic References)。字面量相當於 Java 層面的常量的概念,如文字字串(數字)字面量,宣告為 final 型別的常量等。

符號引用屬於編譯原理中的概念,包含類、方法的資訊:

  • 類和介面的全限定名
  • 欄位名稱和描述符
  • 方法名稱和描述符
執行時常量池

JVM 在完成類載入以後,將類、方法等資訊儲存到元空間(jdk1.8 以前是方法區),會將 class 檔案中的常量池存到堆中(jdk1.7 以前是存在方法區的執行時常量池中)。

執行時常量池相對於 class 檔案常量池另外一個特性是具備動態性,並非預置入 class 檔案中常量池的內容才能進入堆中的執行時常量池,執行期間也可以將新的常量放入池中,比如使用 String 類的 intern() 方法。

Java 示例

code1:

public class StringInternDemo {
    public static void main(String[] args) {
        test1();
    }

    public static void test1() {
        String b = new String("flower"
); String c = new StringBuilder().append("flow").append("er").toString(); System.out.println(c.intern() == c); // false } }

code2:

public class StringInternDemo0 {
    public static void main(String[] args) {
        test2();
    }

    public static void test2() {
        String a =
new String("flower" + "brown"); String c = new StringBuilder().append("flow").append("er").toString(); System.out.println(c.intern() == c); // true } }

為什麼 code1 輸出是 false,而 code2 是true 呢? code1 和 code2 的區別不大啊。前面說過 .class 檔案中有常量池,在完成類載入以後會將類中的常量池儲存到堆中,下面來看看 code1 和 code2 的位元組碼檔案中的常量池。
code1:
在這裡插入圖片描述
code2:
在這裡插入圖片描述
從 code1 和 code2 的 class 常量池中存在 flower 和 flowerbrown。

來看圖示:
code1:
在這裡插入圖片描述
在類載入完以後,堆中的常量池就存在 flower 了(網上很多文章說編譯期就確定了),String b = new String("flower");會建立兩個物件,堆中建立一個,常量池中有一個(如果常量池中存在這個物件了,就不進行建立了)。明顯,c.intern 返回的物件的地址和 c 本身所指向的地址是不一樣的,輸出 false。

code2:
在這裡插入圖片描述
String a = new String("flower" + "brown");會在類載入後在常量池中確定 flowerbrown,此時常量池中是沒有 flower 和 brown 的,所以這行程式碼 也只是建立兩個物件,String:字串常量池 這篇文章說會建立 4 個物件(flower、brown、flowerbrown、new String())其實是不對的。

c.intern() 發現常量池中並不存在 flower,則會在常量池中進行建立,但只是儲存 flower 在堆中的引用,這個引用和 c 都是指向相同的地址,所以這裡輸出是 true。