關於String.intern()(三):new String("str01")和new String("str")+new Strng("01")的區別

文章轉自:https://blog.csdn.net/qq_34115899/article/details/86583262

  • String類和常量池內存分析
  • 8種基本類型的包裝類和常量池

String 類和常量池

1 String 對象的兩種創建方式

String str1 = "abcd"; String str2 = new String("abcd"); System.out.println(str1==str2);//false

記住:只要使用 new 方法,便需要創建新的對象。

2 String 類型的常量池比較特殊。它的主要使用方法有兩種:

  • 直接使用雙引號聲明出來的 String 對象會直接存儲在常量池中。
  • 如果不是用雙引號聲明的 String 對象,可以使用 String 提供的 intern 方法。String.intern() 是一個 Native 方法,它的作用(在JDK1.6和1.7操作不同)是:如果運行時常量池中已經包含一個等於此 String 對象內容的字符串,則返回常量池中該字符串的引用;如果沒有,在jdk1.6中,將此String對象添加到常量池中,然後返回這個String對象的引用(此時引用的串在常量池)。在jdk1.7中,放入一個引用,指向堆中的String對象的地址,返回這個引用地址(此時引用的串在堆)。根據《java虛擬機規範 Java SE 8版》記錄,如果某String實例所包含的Unicode碼點序列與CONSTANT——String_info結構所給出的序列相同,而之前又曾在該實例上面調用過String.intern方法,那麼此次字符串常量獲取的結果將是一個指向相同String實例的引用。這是什麼意思呢?解釋一下,比如在jdk1.7及以後("a"+"b"+"c").intern() == "abc",結果返回true。常量池中有"a", "b", "c", 堆中有連接的"abc",調用intern(),發現字符串常量池沒有"abc",那麼就保存堆中的"abc"引用,接着==右邊的"abc"會在字符串常量池中查找"abc",結果發現有了"abc"引用,那麼獲取結果就是堆中的"abc",兩者是相同位置(都是常量池)的"abc",所以返回true。如果這裏不清楚,請看下面的例子,再返回來看這裏即可。

關於String的intern()問題,可參考這篇文章Java技術——你真的瞭解String類的intern()方法嗎

關於運行時常量池:java虛擬機爲每個類型都維護着一個常量池。該常量池是java虛擬機中的運行時數據結構,像傳統編程語言實現中的符號表一樣有很多用途。當類或接口創建時,它的二進制表示中的常量池表被用來構造運行時常量池,運行時常量池中的所有引用最初都是符號引用。

以下所說常量池爲字符串常量池。

接下來我們均以示例的方式來解釋問題,也是我在某篇文章底下解決的別人問題的筆記。

問題一:

  1. String h = new String("cc");
  2. String intern = h.intern();
  3. System.out.println(intern == h); // 返回false

這裏爲什麼不返回true,而是返回false呢?

解釋:

當new String("cc")後,堆中創建了"cc",也會將"cc"放入常量池,即可以理解爲創建了2個字符串對象。當你String intern = h.intern();其中h.intern()會去常量池檢查是否有了"cc",結果發現有了,那麼此時返回常量池的引用地址給intern,用常量池的引用intern和堆中的h引用去比較肯定不相等。所以返回false。

問題二:

我對以下代碼的操作過程有疑問

  1. String str2 = new String("str") + new String("01");
  2. String str1 = "str01";
  3. str2.intern();
  4. System.out.println(str2 == str1); // false

解釋:

       第一句new String("str") + new String("01");現在在堆中創建了"str",同時放入常量池,創建了"01",同時放入常量池,再進行連接,堆中出現了"str01"。此時常量池中有:"str","01",此時堆中有"str","01","str01"。str2引用指向堆中的"str01"。
       接着第二句String str1 = "str01";直接在常量池創建了"str01"。此時常量池中有:"str","01","str01",此時堆中有"str","01","str01"。str1指向常量池中的"str01"。
       接着第三句str2.intern();檢查常量池是否有"str01",結果發現有了,返回常量池"str01"的地址,很可惜,沒有變量去接收,所以這一句沒什麼用,str2指向也不會改變,還是指向堆中"str01"。 
       第四句去打印str2==str1,一個堆中的"str01"和一個常量池中的"str01"比較,返回false。

問題三:

那這一段代碼呢?

  1. String str2 = new String("str") + new String("01");
  2. String str1 = "str01";
  3. String str3 = str2.intern();
  4. System.out.println(str3 == str1); // true

解釋:

比問題二多了一個str3引用保存了常量池"str01",str3和str1均指向常量池的"str01",所以返回true

問題四:

  1. String str2 = new String("str") + new String("01");
  2. str2.intern();
  3. String str1 = "str01";
  4. System.out.println(str2 == str1);
  5. String str3 = new String("str01");
  6. str3.intern();
  7. String str4 = "str01";
  8. System.out.println(str3 == str4);

這個代碼的過程暈乎了,到底這些串在堆還是在常量池呢?

解釋:

       第一句new String("str") + new String("01");現在在堆中創建了"str",同時放入常量池,創建了"01",同時放入常量池,再進行連接,堆中出現了"str01"。此時常量池中有:"str","01",此時堆中有"str","01","str01"。str2引用指向堆中的"str01"。 
       第二句,str2.intern();檢查到常量池不存在"str01",如果在jdk1.6,那麼就將堆中的"str01"添加到常量池中,如果是jdk1.7,那麼就在常量池保存指向堆中"str01"的地址,即保存堆中"str01"的引用。接下來的講解以jdk1.7爲準!!

       第三句String str1 = "str01";檢查到常量池有一個地址保存了這個串,str1就直接指向這個地址,即還是堆中的"str01"。
       接着打印str2==str1是否相等,str2指向堆中的"str01",str1指向常量池的某個地址,這個地址恰好是保存堆中的"str01",所以仍然是true。
       接着往下看,String str3 = new String("str01");又在堆中創建了"str01",現在堆中有了2個"str01"
       下一句str3.intern(); 去檢查一下常量池到底有沒有"str01"呢?檢查發現常量池有個引用指向堆中的"str01",檢查是用equals比較的,JVM認爲常量池是有"str01"的,那麼返回指向堆中的"str01"地址,很可惜,沒有變量去接收,這一句在這裏沒有什麼用。
       下一句String str4 = "str01";檢查到常量池有個引用指向堆中的"str01",檢查是用equals相比,結果爲true,那麼str4保存這個地址,所以這個"str01"還是堆中的第一個"str01"。
       下一句打印str3==str4,str3是堆中新建的第二個"str01",str4保存的仍是第一個堆中的"str01",兩塊堆的地址,所以返回false。

問題五:

 String str2 = new String("str") + new String("01"); 爲什麼第一行要這麼寫呢? 爲什麼不String str2 = new String("str01");呢? 區別在哪裏呢?

解釋:

       我們來單獨執行比較,前者new String("str")堆中創建"str",常量池沒有"str",所以也要放到常量池,new String("01")在堆中創建"01",也放入到常量池,相加操作只會在堆中創建"str01",所以前者執行以後,內存:堆中有"str","01","str01",常量池中"str","01"。str2引用指向堆中的"str01"。
       現在來看後者String str2 = new String("str01");這個就是在堆中創建"str01"並且放入常量池,str2引用指向堆中的"str01",內存:堆中有"str01",常量池中有"str01"。
       綜上所述,區別就在於這些串處於不同的位置,前者在常量池是沒有"str01"的。

問題六:

  1. String s = new String("abc");
  2. String s1 = "abc";
  3. String s2 = new String("abc");
  4. System.out.println(s == s1);// 堆內存s和常量池內存s1相比,false
  5. System.out.println(s == s2);// 堆內存s和堆內存s2相比,false
  6. System.out.println(s == s1.intern());// 堆內存地址s和常量池地址s1相比,false
  7. System.out.println(s == s2.intern());// 堆內存地址s和常量池地址s1相比,false
  8. System.out.println(s1 == s2.intern());// 常量池地址s1和常量池地址s1相比,true
  9. System.out.println(s.intern() == s2.intern());// 常量池地址s1和常量池地址s1相比,true

解釋:有註釋,無需多餘解釋,上面的問題看懂了這個一看就懂。

問題七:

  1. String s1 = "abc";
  2. String s2 = "a";
  3. String s3 = "bc";
  4. String s4 = s2 + s3;
  5. System.out.println(s1 == s4);//false,因爲s2+s3實際上是使用StringBuilder.append來完成,會生成不同的對象。
  6. // s1指向常量池"abc",s4指向堆中"abc"(append連接而來)
  7. String S1 = "abc";
  8. final String S2 = "a";
  9. final String S3 = "bc";
  10. String S4 = S2 + S3;
  11. System.out.println(S1 == S4);//true,因爲final變量在編譯後會直接替換成對應的值
  12. // 所以實際上等於s4="a"+"bc",而這種情況下,編譯器會直接合併爲s4="abc",所以最終s1==s4。

 

8種基本類型的包裝類和常量池

  • Java 基本類型的包裝類的大部分都實現了常量池技術,即 Byte、Short、Integer、Long、Character、Boolean;這5種包裝類默認創建了數值 [-128,127] 的相應類型的緩存數據,但是超出此範圍仍然會去創建新的對象。
  • 兩種浮點數類型的包裝類 Float、Double 並沒有實現常量池技術。
  1. Integer i1 = 33;
  2. Integer i2 = 33;
  3. System.out.println(i1 == i2);// 輸出true
  4. Integer i11 = 333;
  5. Integer i22 = 333;
  6. System.out.println(i11 == i22);// 輸出false
  7. Double i3 = 1.2;
  8. Double i4 = 1.2;
  9. System.out.println(i3 == i4);// 輸出false

在[-128,127]區間內的利用cache數組的值,否則new一個新的Integer對象。這裏2個333不等因爲是2塊不同的堆內存。2個33相等是因爲利用了同一個cache數組,是值的比較,這裏i1==33,打印出來也是true。

Integer 緩存源代碼:

  1. public static Integer valueOf(int i) {
  2. if (i >= IntegerCache.low && i <= IntegerCache.high)
  3. return IntegerCache.cache[i + (-IntegerCache.low)];
  4. return new Integer(i);
  5. }

 

應用場景:

  1. Integer i1=40;Java 在編譯的時候會直接將代碼封裝成 Integer i1=Integer.valueOf(40); 從而使用常量池中的對象。
  2. Integer i1 = new Integer(40) ;這種情況下會創建新的對象。

Integer i1 = 40; 
Integer i2 = new Integer(40); 
System.out.println(i1==i2); //輸出false

Integer 比較(==)更豐富的一個例子:

  1. Integer i1 = 40;
  2. Integer i2 = 40;
  3. Integer i3 = 0;
  4. Integer i4 = new Integer(40);
  5. Integer i5 = new Integer(40);
  6. Integer i6 = new Integer(0);
  7. System.out.println("i1=i2   " + (i1 == i2));
  8. System.out.println("i1=i2+i3   " + (i1 == i2 + i3));
  9. System.out.println("i1=i4   " + (i1 == i4));
  10. System.out.println("i4=i5   " + (i4 == i5));
  11. System.out.println("i4=i5+i6   " + (i4 == i5 + i6));
  12. System.out.println("40=i5+i6   " + (40 == i5 + i6));

結果:

i1=i2   true

i1=i2+i3   true

i1=i4   false

i4=i5   false

i4=i5+i6   true

40=i5+i6   true

解釋:

語句 i4 == i5 + i6,因爲 + 這個操作符不適用於 Integer 對象,首先 i5 和 i6 進行自動拆箱操作,進行數值相加,即 i4 == 40。然後Integer對象無法與數值進行直接比較,所以i4自動拆箱轉爲int值40,最終這條語句轉爲40 == 40進行數值比較。

 

===============Talk is cheap, show me the code================

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章