創建String的幾種方式以及背后的存取規則:
1,String str1 = new String("1900");(后面不帶+),這個過程中,實際上有兩個對象生成,一是在堆上創建了"1900"這個字符串對象,同時,檢查常量池,池中如果有"1900",不管是指向"1900"的引用還是實打實的串,就不創建新的"1900",如果沒有,則創建"1900"放入常量池,暫且不討論常量池因為JDK版本的不同而導致的位置不同。
2,String str2 = "1900";此時直接將"1900"加入常量池,同樣,檢查有沒有,有則返回引用沒有則加入。
3,常量字符串的"+"拼接操作,如String str3 = "19"+"00";同樣,檢查常量池,從而決定創建還是引用。
4,final修飾的串,final修飾的串在編譯器就會替換成常量,即等號后面是啥,就將它送去常量池。當然,同上面一樣,也需要檢查常量池。
5,String str4 = str3 + "hello";這種變量+常量的形式,則會調用StringBuilder在堆上創建對象。注意是堆。
關於JDK版本導致的intern()方法的不同:
JDK1.6及以前。intern()檢查常量池如果發現未存在放入常量池的是字符串。
JDK1.7及以后。intern()檢查常量池如果發現未存在放入常量池的是其在堆上的對象的引用,或者換個說法,存的是堆上對象的一個一模一樣的副本。
幾個常見的問題及其解釋:(以JDK1.7+為標准 )
我在蠻多博客都看到了這個問題的描述,可是看得我暈頭轉向,不知所措......
問題1:
String str1 = new String("1900"); String intern = str1.intern(); System.out.println(intern == str1);
第三行輸出什么?答案是false。
解釋:第一行在堆中創建了"1900",同時在常量池也有"1900",第二行調用intern,查看常量池,發現常量池有"1900",所以返回常量池這個對象的引用,而第三行比較的即是在堆上的該對象的引用和在常量池中該對象的引用,自然是不等。
問題二:
String str1 = new String("19") + new String("00"); String str2 = "1900"; str1.intern(); System.out.println(str2 == str1);
第三行輸出什么呢?答案還是false
解釋:剛開始我也想了挺久為啥是false,原因是我的閱讀理解有問題或者說粗心
首先是第一行,可以分為兩步來看,第一步是在堆上創建了兩個對象,一個是"19",一個是"00",同時跟之前一樣,常量池中創建了"19"和"00"兩個對象,第二步,也就是連接的步驟,是指在對上創建了“1900”,而常量池並沒有"1900"。
第二行用字面值賦值創建str2,就會先檢查常量池,發現並沒有“1900”,所以它在常量池創建了“1900”。
第三行也是當時最誤導我的地方,str1.intern(),先檢查常量池,此時常量池有“1900”,所以返回了“1900”的引用,但是這里是返回值,不是將str2的地址改變,而此處沒有變量來接住返回值,所以這一行並沒有什么卵用。
故第四行一個是堆上的str1,一個是常量池的str2,自然不等。
問題三:改為由變量接住
String str1 = new String("19") + new String("00"); String str2 = "1900"; String str3 = str1.intern(); System.out.println(str2 == str3);
顯然這里會返回true,解釋就不多解釋了。
問題四:
new String("1900")和new String("19")+new String("00")有啥不同?
區別在於,其實前面也說過,new String("1900")之后,在堆里,常量池都會有"1900",但是如果是后者,那么在常量池中是沒有"1900"的。
問題五:
String str1 = new String("19") + new String("00"); str1.intern(); String str2 = "1900"; System.out.println(str2 == str1); String str3 = new String("1900"); str3.intern(); String str4 = "1900"; System.out.println(str3 == str4);
答案:
true false
有了以上的鋪墊,這里就好分析了,首先前四行,第一行運行過后,堆中有"19","00","1900",常量池中有"19","00",經過第二行,由於JDK1.7+ intern的特性,在常量池中生成了一個str1的副本,到第三行,檢查常量池,發現有"1900"(調用equals()),於是返回該引用,而這個引用就是sr1 ,所以兩者相等。
后面四行,堆上創建了另外一個值也為"1900"的str3,而這時候調用intern,本來常量池中有"1900",所以這一行沒起作用,str4則是引用了常量池中的"1900",顯然Str3和 str4不等。
問題六:
關於final,以及"+"
String s1 = "abc"; String s2 = "a"; String s3 = "bc"; String s4 = s2 + s3; System.out.println(s1 == s4); String s5 = "abc"; final String final_str1 = "a"; final String final_str2 = "bc"; String s6 = final_str1 + final_str2; System.out.println(s5 == s6);
答案:
false true
分析:
前五行:前三行都是直接在常量池創建字符串,第三行拼接實際是調用StringBuilder,建立在堆上,所以s1和s4不等。
后五行:final修飾的會在編譯器被替換為常量,所以s6相當於兩個常量字符串相加(s6="a"+"bc"),根據前面的規則,直接在常量池中加入,而在這之前常量池中有abc,所以自然是相等的。
總之,如果是連接變量和常量,則JVM是無法優化的,則創建在堆上,除非用final修飾。
總結:
以上的機制都是JVM為了節約內存所做的優化,實際上細心的會發現,各大包裝類中都有類似機制。比如Integer類里面:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
在-128到128之間的數是直接返回的,而其他則是創建新對象。
最后,感謝兩位大佬的博客,讓小弟受益匪淺:
https://blog.csdn.net/qq_34115899/article/details/86583262
https://blog.csdn.net/seu_calvin/article/details/52291082