Java技術——你真的了解String類的intern()方法嗎


樓主說一下 - 重點: 

  intern() 這個api 只有在 new String("SEU")+ new String("Calvin"); 這種情況下 有個坑 (jdk.16 和1.7 有改動),其他普通情況下 比如:

    String str1 = new String("SEU");

               str.intern(); 這種情況 該API 還是和以前一樣。

接下來可以看以下介紹:

0.引言

什么都先不說,先看下面這個引入的例子:

[java] view plain copy
 
print?
  1. String str1 = new String("SEU")+ new String("Calvin");      
  2. System.out.println(str1.intern() == str1);   
  3. System.out.println(str1 == "SEUCalvin");  
String str1 = new String("SEU")+ new String("Calvin");    
System.out.println(str1.intern() == str1); 
System.out.println(str1 == "SEUCalvin");

 

本人JDK版本1.8,輸出結果為:

 

[java] view plain copy
 
print?
  1. true  
  2. true  
true
true

再將上面的例子加上一行代碼:

[java] view plain copy
 
print?
  1. String str2 = "SEUCalvin";//新加的一行代碼,其余不變  
  2. String str1 = new String("SEU")+ new String("Calvin");      
  3. System.out.println(str1.intern() == str1);   
  4. System.out.println(str1 == "SEUCalvin");   
String str2 = "SEUCalvin";//新加的一行代碼,其余不變
String str1 = new String("SEU")+ new String("Calvin");    
System.out.println(str1.intern() == str1); 
System.out.println(str1 == "SEUCalvin"); 

再運行,結果為:

 

 

[java] view plain copy
 
print?
  1. false  
  2. false  
false
false

是不是感覺莫名其妙,新定義的str2好像和str1沒有半毛錢的關系,怎么會影響到有關str1的輸出結果呢?其實這都是intern()方法搞的鬼!看完這篇文章,你就會明白。o(∩_∩)o 

說實話我本來想總結一篇Android內存泄漏的文章的,查閱了很多資料,發現不得不從Java的OOM講起,講Java的OOM又不得不講Java的虛擬機架構。如果不了解JVM的同學可以查看此篇 JVM——Java虛擬機架構。(這篇文章已經被我修改過N多次了,個人感覺還是挺全面清晰的,每次看都會有新的理解。)

在JVM架構一文中也有介紹,在JVM運行時數據區中的方法區有一個常量池,但是發現在JDK1.6以后常量池被放置在了堆空間,因此常量池位置的不同影響到了String的intern()方法的表現。深入了解后發現還是值得寫下來記錄一下的。為了確保文章的實時更新,實時修改可能出錯的地方,請確保這篇是原文,而不是無腦轉載來的“原創文”,原文鏈接為:SEU_Calvin的博客

 

 

1.為什么要介紹intern()方法

intern()方法設計的初衷,就是重用String對象,以節省內存消耗。以前是要在常量池中創建字符串,現在1.7 並沒有這么做(值針對樓主一開始說的特殊情況2個 String 相+)

2.深入認識intern()方法

JDK1.7后,常量池被放入到堆空間中,這導致intern()函數的功能不同,具體怎么個不同法,且看看下面代碼,這個例子是網上流傳較廣的一個例子,分析圖也是直接粘貼過來的,這里我會用自己的理解去解釋這個例子:

[java] view plain copy
 
print?
  1. String s = new String("1");  
  2. s.intern();  
  3. String s2 = "1";  
  4. System.out.println(s == s2);  
  5.   
  6. String s3 = new String("1") + new String("1");  
  7. s3.intern();  
  8. String s4 = "11";  
  9. System.out.println(s3 == s4);  
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);

輸出結果為:

[java] view plain copy
 
print?
  1. JDK1.6以及以下:false false  
  2. JDK1.7以及以上:false true  
JDK1.6以及以下:false false
JDK1.7以及以上:false true

再分別調整上面代碼2.3行、7.8行的順序:

[java] view plain copy
 
print?
  1. String s = new String("1");  
  2. String s2 = "1";  
  3. s.intern();  
  4. System.out.println(s == s2);  
  5.   
  6. String s3 = new String("1") + new String("1");  
  7. String s4 = "11";  
  8. s3.intern();  
  9. System.out.println(s3 == s4);  
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);

輸出結果為:

 

[java] view plain copy
 
print?
  1. JDK1.6以及以下:false false  
  2. JDK1.7以及以上:false false  
JDK1.6以及以下:false false
JDK1.7以及以上:false false

 

下面依據上面代碼對intern()方法進行分析:

 

2.1 JDK1.6


 

在JDK1.6中所有的輸出結果都是 false,因為JDK1.6以及以前版本中,常量池是放在 Perm 區(屬於方法區)中的,熟悉JVM的話應該知道這是和堆區完全分開的。

使用引號聲明的字符串都是會直接在字符串常量池中生成的,而 new 出來的 String 對象是放在堆空間中的。所以兩者的內存地址肯定是不相同的,即使調用了intern()方法也是不影響的。如果不清楚String類的“==”和equals()的區別可以查看我的這篇博文Java面試——從Java堆、棧角度比較equals和==的區別

intern()方法在JDK1.6中的作用是:比如String s = new String("SEU_Calvin"),再調用s.intern(),此時返回值還是字符串"SEU_Calvin",表面上看起來好像這個方法沒什么用處。但實際上,在JDK1.6中它做了個小動作:檢查字符串池里是否存在"SEU_Calvin"這么一個字符串,如果存在,就返回池里的字符串;如果不存在,該方法會把"SEU_Calvin"添加到字符串池中,然后再返回它的引用。然而在JDK1.7中卻不是這樣的,后面會討論。

 

2.2 JDK1.7

 

針對JDK1.7以及以上的版本,我們將上面兩段代碼分開討論。先看第一段代碼的情況:


 

再把第一段代碼貼一下便於查看:

[java] view plain copy
 
print?
  1. String s = new String("1");  
  2. s.intern();  
  3. String s2 = "1";  
  4. System.out.println(s == s2);  
  5.   
  6. String s3 = new String("1") + new String("1");  
  7. s3.intern();  
  8. String s4 = "11";  
  9. System.out.println(s3 == s4);  
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);

 

String s = newString("1"),生成了常量池中的“1” 和堆空間中的字符串對象。

s.intern(),這一行的作用是s對象去常量池中尋找后發現"1"已經存在於常量池中了。

String s2 = "1",這行代碼是生成一個s2的引用指向常量池中的“1”對象。

結果就是 s 和 s2 的引用地址明顯不同。因此返回了false。

 

重點就在 這個 ‘’+‘’ ;

String s3 = new String("1") + newString("1"),這行代碼在字符串常量池中生成“1” ,並在堆空間中生成s3引用指向的對象(內容為"11")。注意此時常量池中是沒有 “11”對象的。

s3.intern(),這一行代碼,是將 s3中的“11”字符串放入 String 常量池中,此時常量池中不存在“11”字符串,JDK1.6的做法是直接在常量池中生成一個 "11" 的對象。

但是在JDK1.7中,常量池中不需要再存儲一份對象了,可以直接存儲中的引用。這份引用直接指向 s3 引用的對象,也就是說s3.intern() ==s3會返回true。

String s4 = "11", 這一行代碼會直接去常量池中創建,但是發現已經有這個對象了,此時也就是指向 s3 引用對象的一個引用。因此s3 == s4返回了true。

 

下面繼續分析第二段代碼:

再把第二段代碼貼一下便於查看:

[java] view plain copy
 
print?
  1. String s = new String("1");  
  2. String s2 = "1";  
  3. s.intern();  
  4. System.out.println(s == s2);  
  5.   
  6. String s3 = new String("1") + new String("1");  
  7. String s4 = "11";  
  8. s3.intern();  
  9. System.out.println(s3 == s4);  
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);

String s = newString("1"),生成了常量池中的“1” 和堆空間中的字符串對象。

String s2 = "1",這行代碼是生成一個s2的引用指向常量池中的“1”對象,但是發現已經存在了,那么就直接指向了它。

s.intern(),這一行在這里就沒什么實際作用了。因為"1"已經存在了。

結果就是 s 和 s2 的引用地址明顯不同。因此返回了false。

 

 

String s3 = new String("1") + newString("1"),這行代碼在字符串常量池中生成“1” ,並在堆空間中生成s3引用指向的對象(內容為"11")。注意此時常量池中是沒有 “11”對象的。

String s4 = "11", 這一行代碼會直接去生成常量池中的"11"。

s3.intern(),這一行在這里就沒什么實際作用了。因為"11"已經存在了。

結果就是 s3 和 s4 的引用地址明顯不同。因此返回了false。

為了確保文章的實時更新,實時修改可能出錯的地方,請確保這篇是原文,而不是無腦轉載來的“原創文”,原文鏈接為:SEU_Calvin的博客

 

 

3 總結

終於要做Ending了。現在再來看一下開篇給的引入例子,是不是就很清晰了呢。

 

[java] view plain copy
 
print?
  1. String str1 = new String("SEU") + new String("Calvin");        
  2. System.out.println(str1.intern() == str1);     
  3. System.out.println(str1 == "SEUCalvin");    
String str1 = new String("SEU") + new String("Calvin");      
System.out.println(str1.intern() == str1);   
System.out.println(str1 == "SEUCalvin");  

 

str1.intern() == str1就是上面例子中的情況,str1.intern()發現常量池中不存在“SEUCalvin”,因此指向了str1。 "SEUCalvin"在常量池中創建時,也就直接指向了str1了。兩個都返回true就理所當然啦。

那么第二段代碼呢:

 

[java] view plain copy
 
print?
  1. String str2 = "SEUCalvin";//新加的一行代碼,其余不變  
  2. String str1 = new String("SEU")+ new String("Calvin");      
  3. System.out.println(str1.intern() == str1);   
  4. System.out.println(str1 == "SEUCalvin");   
String str2 = "SEUCalvin";//新加的一行代碼,其余不變
String str1 = new String("SEU")+ new String("Calvin");    
System.out.println(str1.intern() == str1); 
System.out.println(str1 == "SEUCalvin"); 

也很簡單啦,str2先在常量池中創建了“SEUCalvin”,那么str1.intern()當然就直接指向了str2,你可以去驗證它們兩個是返回的true。后面的"SEUCalvin"也一樣指向str2。所以誰都不搭理在堆空間中的str1了,所以都返回了false。

 

好了,本篇對intern的作用以及在JDK1.6和1.7中的實現原理的介紹就到此為止了。希望能給你帶來幫助。

轉載請注明出處http://blog.csdn.net/seu_calvin/article/details/52291082


免責聲明!

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



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