1. 程式人生 > 其它 >String#intern理解

String#intern理解

參考文章:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

String型別的常量池的主要使用方法有兩種:

  • 直接使用雙引號宣告出來的String物件會直接儲存在常量池中。
  • 如果不是用雙引號宣告的String物件,可以使用String提供的intern方法。intern 方法會從字串常量池中查詢當前字串是否存在,若不存在就會將當前字串放入常量池中

String s = new String("abc")這個語句建立了幾個物件

建立了2個物件,第一個物件是”abc”字串儲存在常量池中,第二個物件在JAVA Heap中的 String 物件。

看兩段程式碼:

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

列印結果是

  • jdk6 下false false
  • jdk7 下false true

然後將s3.intern();語句下調一行,放到String s4 = "11";後面。將s.intern(); 放到String s2 = "1";後面。

public static void main(String[] args) {
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();
    System.out.println(s3 == s4);
}

jdk6的解釋

注:圖中綠色線條代表 string 物件的內容指向。 黑色線條代表地址指向。

jdk6中的常量池是放在 Perm 區中的,Perm 區和正常的 JAVA Heap 區域是完全分開的。上面說過如果是使用引號宣告的字串都是會直接在字串常量池中生成,而 new 出來的 String 物件是放在 JAVA Heap 區域。所以拿一個 JAVA Heap 區域的物件地址和字串常量池的物件地址進行比較肯定是不相同的,即使呼叫String.intern方法也是沒有任何關係的。

jdk7的解釋

在 Jdk6 以及以前的版本中,字串的常量池是放在堆的 Perm 區的;在 jdk7 的版本中,字串常量池已經從 Perm 區移到正常的 Java Heap 區域了; jdk8 已經直接取消了 Perm 區域,而新建立了一個元區域。

字串常量池移動到 JAVA Heap 區域後。針對上述第一段程式碼:

  • 先看看 s 和 s2 物件。String s = new String("1"); 第一句程式碼,生成了2個物件。常量池中的“1” 和 JAVA Heap 中的字串物件。s.intern(); 這一句是 s 物件去常量池中尋找後發現 “1” 已經在常量池裡了,所以直接返回在常量池中的引用了。
  • 接下來String s2 = "1"; 這句程式碼是生成一個 s2的引用指向常量池中的“1”物件。 結果就是 s 和 s2 的引用地址明顯不同。
  • 再看看 s3和s4字串。String s3 = new String("1") + new String("1");,這句程式碼中現在生成了2最終個物件,是字串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的物件。此時s3引用物件內容是”11”,但此時常量池中是沒有 “11”物件的。
  • 接下來s3.intern();是將 s3中的“11”字串放入 String 常量池中,因為此時常量池中不存在“11”字串。常量池中不需要再儲存一份物件了,可以直接儲存堆中的引用。這份引用指向 s3 引用的物件。 也就是說引用地址是相同的。
  • 最後String s4 = "11"; 這句程式碼中”11”是顯示宣告的,因此會直接去常量池中建立,建立的時候發現已經有這個物件了,此時也就是指向 s3 引用物件的一個引用。所以 s4 引用就指向和 s3 一樣了。因此最後的比較 s3 == s4 是 true。

針對第二段程式碼:

  • 來看第二段程式碼,從上邊第二幅圖中觀察。第一段程式碼和第二段程式碼的改變就是 s3.intern(); 的順序是放在String s4 = "11";後了。這樣,首先執行String s4 = "11";宣告 s4 的時候常量池中是不存在“11”物件的,執行完畢後,“11“物件是 s4 宣告產生的新物件。然後再執行s3.intern();時,常量池中“11”物件已經存在了,因此 s3 和 s4 的引用是不同的。
  • 第二段程式碼中的 s 和 s2 程式碼中,s.intern();,這一句往後放也不會有什麼影響了,因為物件池中在執行第一句程式碼String s = new String("1");的時候已經生成“1”物件了。下邊的s2宣告都是直接從常量池中取地址引用的。 s 和 s2 的引用地址是不會相等的。

總結:

jdk7 版本對 intern 操作和常量池都做了一定的修改。主要包括2點:

  • 將String常量池 從 Perm 區移動到了 Java Heap區
  • String#intern 方法時,如果存在堆中的物件,會直接儲存物件的引用,而不會重新建立物件。