文章轉自: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虛擬機中的運行時數據結構,像傳統編程語言實現中的符號表一樣有很多用途。當類或接口創建時,它的二進制表示中的常量池表被用來構造運行時常量池,運行時常量池中的所有引用最初都是符號引用。
以下所說常量池爲字符串常量池。
接下來我們均以示例的方式來解釋問題,也是我在某篇文章底下解決的別人問題的筆記。
問題一:
- String h = new String("cc");
- String intern = h.intern();
- 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。
問題二:
我對以下代碼的操作過程有疑問
- String str2 = new String("str") + new String("01");
- String str1 = "str01";
- str2.intern();
- 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。
問題三:
那這一段代碼呢?
- String str2 = new String("str") + new String("01");
- String str1 = "str01";
- String str3 = str2.intern();
- System.out.println(str3 == str1); // true
解釋:
比問題二多了一個str3引用保存了常量池"str01",str3和str1均指向常量池的"str01",所以返回true
問題四:
- String str2 = new String("str") + new String("01");
- str2.intern();
- String str1 = "str01";
- System.out.println(str2 == str1);
-
- String str3 = new String("str01");
- str3.intern();
- String str4 = "str01";
- 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"的。
問題六:
- String s = new String("abc");
- String s1 = "abc";
- String s2 = new String("abc");
- System.out.println(s == s1);// 堆內存s和常量池內存s1相比,false
- System.out.println(s == s2);// 堆內存s和堆內存s2相比,false
- System.out.println(s == s1.intern());// 堆內存地址s和常量池地址s1相比,false
- System.out.println(s == s2.intern());// 堆內存地址s和常量池地址s1相比,false
- System.out.println(s1 == s2.intern());// 常量池地址s1和常量池地址s1相比,true
- System.out.println(s.intern() == s2.intern());// 常量池地址s1和常量池地址s1相比,true
解釋:有註釋,無需多餘解釋,上面的問題看懂了這個一看就懂。
問題七:
- String s1 = "abc";
- String s2 = "a";
- String s3 = "bc";
- String s4 = s2 + s3;
- System.out.println(s1 == s4);//false,因爲s2+s3實際上是使用StringBuilder.append來完成,會生成不同的對象。
- // s1指向常量池"abc",s4指向堆中"abc"(append連接而來)
- String S1 = "abc";
- final String S2 = "a";
- final String S3 = "bc";
- String S4 = S2 + S3;
- System.out.println(S1 == S4);//true,因爲final變量在編譯後會直接替換成對應的值
- // 所以實際上等於s4="a"+"bc",而這種情況下,編譯器會直接合併爲s4="abc",所以最終s1==s4。
8種基本類型的包裝類和常量池
- Java 基本類型的包裝類的大部分都實現了常量池技術,即 Byte、Short、Integer、Long、Character、Boolean;這5種包裝類默認創建了數值 [-128,127] 的相應類型的緩存數據,但是超出此範圍仍然會去創建新的對象。
- 兩種浮點數類型的包裝類 Float、Double 並沒有實現常量池技術。
- Integer i1 = 33;
- Integer i2 = 33;
- System.out.println(i1 == i2);// 輸出true
- Integer i11 = 333;
- Integer i22 = 333;
- System.out.println(i11 == i22);// 輸出false
- Double i3 = 1.2;
- Double i4 = 1.2;
- System.out.println(i3 == i4);// 輸出false
在[-128,127]區間內的利用cache數組的值,否則new一個新的Integer對象。這裏2個333不等因爲是2塊不同的堆內存。2個33相等是因爲利用了同一個cache數組,是值的比較,這裏i1==33,打印出來也是true。
Integer 緩存源代碼:
- public static Integer valueOf(int i) {
- if (i >= IntegerCache.low && i <= IntegerCache.high)
- return IntegerCache.cache[i + (-IntegerCache.low)];
- return new Integer(i);
- }
應用場景:
- Integer i1=40;Java 在編譯的時候會直接將代碼封裝成 Integer i1=Integer.valueOf(40); 從而使用常量池中的對象。
- Integer i1 = new Integer(40) ;這種情況下會創建新的對象。
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2); //輸出false
Integer 比較(==)更豐富的一個例子:
- Integer i1 = 40;
- Integer i2 = 40;
- Integer i3 = 0;
- Integer i4 = new Integer(40);
- Integer i5 = new Integer(40);
- Integer i6 = new Integer(0);
- System.out.println("i1=i2 " + (i1 == i2));
- System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
- System.out.println("i1=i4 " + (i1 == i4));
- System.out.println("i4=i5 " + (i4 == i5));
- System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
- 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================