淺析java中的string


  在學習java36講的時候看到評論區有人提出的一個問題:

String s1 = new String("do");
s1.intern();
String s2 = "do";

System.out.println(s1 == s2);//false

String s3 = new String("12") + new String("34");
s3.intern();
String s4 = "1234";

System.out.println(s3 == s4);//true     

 

  這個問題主要是考察的內容如果沒有接觸過會覺得有點懵,接下來參考別人的理解再進行一下解釋。

  在 JAVA 語言中有8中基本類型和一種比較特殊的類型String。這些類型為了使他們在運行過程中速度更快,更節省內存,都提供了一種常量池的概念。常量池就類似一個JAVA系統級別提供的緩存。

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

  首先要理解 String s = new String("abc")這個語句創建了幾個對象。 這個題目主要就是為了考察對字符串對象的常量池掌握與否。上述的語句中是創建了2個對象,第一個對象是”abc”字符串存儲在常量池中,第二個對象在JAVA Heap中的 String 對象。

  答案應該是1個或者2個。

  1個的情況:如果字符串池中已經存在了"abc"這個對象,那么直接在創建一個對象放入堆中,返回str引用。

  2個的情況:如果字符串池中未找到"abd"這個對象,那么分別在堆中和字符串池中創建一個對象,字符串池中的比較都是采用equals()方法。

接下來我們主要來談一下String#intern方法。

來看一段代碼:

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 下false false

  • jdk7 下false false

####1,jdk6中的解釋

jdk6圖

jdk6圖

 

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

如上圖所示。首先說一下 jdk6中的情況,在 jdk6中上述的所有打印都是 false 的,因為 jdk6中的常量池是放在 Perm 區中的,Perm 區和正常的 JAVA Heap 區域是完全分開的。上面說過如果是使用引號聲明的字符串都是會直接在字符串常量池中生成,而 new 出來的 String 對象是放在 JAVA Heap 區域。所以拿一個 JAVA Heap 區域的對象地址和字符串常量池的對象地址進行比較肯定是不相同的,即使調用String.intern方法也是沒有任何關系的。

####2,jdk7中的解釋

再說說 jdk7 中的情況。這里要明確一點的是,在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的 Perm 區的,Perm 區是一個類靜態的區域,主要存儲一些加載類的信息,常量池,方法片段等內容,默認大小只有4m,一旦常量池中大量使用 intern 是會直接產生java.lang.OutOfMemoryError: PermGen space錯誤的。 所以在 jdk7 的版本中,字符串常量池已經從 Perm 區移到正常的 Java Heap 區域了。為什么要移動,Perm 區域太小是一個主要原因,當然據消息稱 jdk8 已經直接取消了 Perm 區域,而新建立了一個元區域。應該是 jdk 開發者認為 Perm 區域已經不適合現在 JAVA 的發展了。

正式因為字符串常量池移動到 JAVA Heap 區域后,再來解釋為什么會有上述的打印結果。

jdk7圖1

jdk7圖1

 

  • 在第一段代碼中,先看 s3和s4字符串。String s3 = new String("1") + new String("1");,這句代碼中現在生成了2最終個對象,是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的對象。中間還有2個匿名的new String("1")我們不去討論它們。此時s3引用對象內容是”11”,但此時常量池中是沒有 “11”對象的。
  • 接下來s3.intern();這一句代碼,是將 s3中的“11”字符串放入 String 常量池中,因為此時常量池中不存在“11”字符串,因此常規做法是跟 jdk6 圖中表示的那樣,在常量池中生成一個 “11” 的對象,關鍵點是 jdk7 中常量池不在 Perm 區域了,這塊做了調整。常量池中不需要再存儲一份對象了,可以直接存儲堆中的引用。這份引用指向 s3 引用的對象。 也就是說引用地址是相同的。
  • 最后String s4 = "11"; 這句代碼中”11”是顯示聲明的,因此會直接去常量池中創建,創建的時候發現已經有這個對象了,此時也就是指向 s3 引用對象的一個引用。所以 s4 引用就指向和 s3 一樣了。因此最后的比較 s3 == s4 是 true。

  • 再看 s 和 s2 對象。 String s = new String("1"); 第一句代碼,生成了2個對象。常量池中的“1” 和 JAVA Heap 中的字符串對象。s.intern(); 這一句是 s 對象去常量池中尋找后發現 “1” 已經在常量池里了。

  • 接下來String s2 = "1"; 這句代碼是生成一個 s2的引用指向常量池中的“1”對象。 結果就是 s 和 s2 的引用地址明顯不同。圖中畫的很清晰。

jdk7圖2

jdk7圖2

 

  • 來看第二段代碼,從上邊第二幅圖中觀察。第一段代碼和第二段代碼的改變就是 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 方法時,如果存在堆中的對象,會直接保存對象的引用,而不會重新創建對象。

附上原文鏈接:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM