問題
以下字符串對比的結(jié)果是什么?
String s1 = "ni" + "hao";
String s2 = "ni";
s2 += "hao";
String s3 = "nihao";
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s2 == s3);
答:false true false
我們來看編譯器編譯之后的代碼:
String s1 = "nihao";
String s2 = "ni";
s2 = s2 + "hao";
String s3 = "nihao";
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s2 == s3);
很顯然s1在經(jīng)過編譯器優(yōu)化之后独悴,他們的內(nèi)容相同,都是"nihao"逾苫。接下來我們理解一下對于String來說==
到底比較的是什么声怔。
JAVA中的==
java中的數(shù)據(jù)類型堂鲤,可分為兩類:
1.基本數(shù)據(jù)類型
锤窑,也稱原始數(shù)據(jù)類型甚淡。byte,short,char,int,long,float,double,boolean 他們之間的比較演怎,應用雙等號(==),比較的是他們的值匕争。
2.復合數(shù)據(jù)類型(類)
當他們用(==)進行比較的時候,比較的是他們在內(nèi)存中的存放地址爷耀,所以甘桑,除非是同一個new出來的對象,他們的比較后的結(jié)果為true歹叮,否則比較后結(jié)果為false跑杭。
那么s1和s3是否指向內(nèi)存中同一個對象呢?
字符串常量池
字符串的分配咆耿,和其他的對象分配一樣德谅,耗費高昂的時間與空間代價,作為最基礎(chǔ)的數(shù)據(jù)類型萨螺,大量頻繁的創(chuàng)建字符串窄做,極大程度地影響程序的性能。JVM為了提高性能和減少內(nèi)存開銷慰技,在實例化字符串常量的時候進行了一些優(yōu)化為字符串開辟一個字符串常量池椭盏,類似于緩存區(qū)。創(chuàng)建字符串常量時惹盼,首先檢查字符串常量池是否存在該字符串:存在該字符串庸汗,返回引用實例;不存在手报,實例化該字符串并放入池中蚯舱。
常量池的實現(xiàn)
字符串常量池則存在于方法區(qū)改化,可以被所有線程共享。.jdk1.6 方法區(qū)放在永久代(java堆的一部分)枉昏,jdk1.7以后特別將字符串常量池移動到了的堆內(nèi)存中(使用參數(shù)-XX:PermSize 和-XX:MaxPermSize指定大谐赂亍)。所以導致string的intern方法因為以上變化在不同版本會有不同表現(xiàn)兄裂。
以下代碼在內(nèi)存中存放的結(jié)構(gòu)如圖所示:
String str1 = “abc”;
String str2 = “abc”;
String str3 = “abc”;
String str4 = new String(“abc”);
String str5 = new String(“abc”);
那么String str4 = new String(“abc”) 創(chuàng)建多少個對象句旱?首先在常量池中查找是否有“abc”對象有則返回對應的引用實例,如果沒有則在常量池中創(chuàng)建對應的實例對象晰奖,同時在堆中 new 一個 String("abc") 對象谈撒,將對象地址賦值給str4,創(chuàng)建一個引用。
String的intern方法
從上文中介紹可以知道匾南,以下代碼
String str1 = “abc”;
String str4 = new String(“abc”);
其中str1 == str4判斷會返回false啃匿,因為str1指向常量池中的對象,而str4指向堆中的對象蛆楞。但是我們可以使用intern方法溯乒,重新獲取一個常量池里的對象。
String str1 = “abc”;
String str4 = new String(“abc”).intern();
這樣的話str1 == str4判斷就會返回true了豹爹。如果在循環(huán)中多次要使用到String裆悄,可以直接從常量池中獲取到這個字符串使用,這樣就可以大大減少堆內(nèi)存上的開銷臂聋。
查看常量池
使用Idea中的JclassLib插件查看常量池光稼,我們依然以如下代碼為例:
String s1 = "ni" + "hao";
String s2 = "ni";
s2 += "hao";
String s3 = "nihao";
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s2 == s3);
聲明s1之后,常量池中創(chuàng)建了123的字符串孩等。
聲明s2之后钟哥,常量池中創(chuàng)建了12的字符串。
所以這兩者在常量池中的對象是不同的瞎访,但這個就是我們最終的結(jié)論嗎?顯然不是吁恍。
分析字節(jié)碼
我們用jClassLib分析class文件的字節(jié)碼:
從上圖中可以看到s2 += "hao";
這句代碼扒秸,其實在JVM中運行時,創(chuàng)建了一個StringBuilder對象冀瓦,然后使用append方法添加了s2的字符串伴奥,再添加"hao"
字符串,最后調(diào)用toString的方法生成最終結(jié)果翼闽。所以s2的字符串是使用StringBuilder創(chuàng)建的新對象拾徙,和常量池中的"123"自然就沒有關(guān)系了。
總結(jié):
對于s1來說感局,編譯器將其優(yōu)化為s1="123"
,在常量池中創(chuàng)建對象尼啡。
對于s2來說暂衡,創(chuàng)建"12"
的常量池對象,同時使用StringBuilder創(chuàng)建新對象崖瞭。
對于s3來說狂巢,常量池中已有"123",直接指向常量池中的對象。
所以結(jié)果為false true false书聚。