最近到廣州某互聯網公司面試,當時面試官問假設有兩個字符串String a="abc",String b = "abc";問輸出a==b是true還是false。我當時毫不猶豫答了true,然后根據字符串常量池的知識點結合jvm的內存模型講解,然而他卻跟我說是false,說這是最基本的問題。我當時一臉懵逼,跟他討論了很長時間,后來發現他是錯的,他說a,b兩個變量是存在棧中,這兩個引用是不一樣的,只不過它們指向的內容是一樣的。因為他畢竟工作了好幾年,我當時也質疑我的知識點,所以當時也接受了他的答案。回來上機實踐查資料求證之后,說明我是對的,具體內容整理如下。首先先上三份代碼:
第一段:
public static void main(String[] args) { String s1 = "hello"; String s2 = "hello"; String s3 = "he" + "llo"; String s4 = "hel" + new String("lo"); String s5 = new String("hello"); String s6 = s5.intern(); String s7 = "h"; String s8 = "ello"; String s9 = s7 + s8; System.out.println(s1==s2);//true System.out.println(s1==s3);//true System.out.println(s1==s4);//false System.out.println(s1==s9);//false System.out.println(s4==s5);//false System.out.println(s1==s6);//true }
在jdk1.6,1.7,1.8下運行的結果為:
System.out.println(s1==s2);//true
System.out.println(s1==s3);//true
System.out.println(s1==s4);//false
System.out.println(s1==s9);//false
System.out.println(s4==s5);//false
System.out.println(s1==s6);//true
第二段:
public static void main(String[] args) { String s1 = new String("hello"); String intern1 = s1.intern(); String s2 = "hello"; System.out.println(s1 == s2); String s3 = new String("hello") + new String("hello"); String intern3 = s3.intern(); String s4 = "hellohello"; System.out.println(s3 == s4); }
在jdk1.6下運行的結果為:
false,false
在jdk1.7,1.8下運行的結果為:
false,true
第三段:
public static void main(String[] args) { String s1 = new String("hello"); String s2 = "hello"; String intern1 = s1.intern(); System.out.println(s1 == s2); String s3 = new String("hello") + new String("hello"); String s4 = "hellohello"; String intern3 = s3.intern(); System.out.println(s3 == s4); }
在jdk1.6下運行的結果為:
false,false
在jdk1.7,1.8下運行的結果為:
false,false
第一段代碼:
String類的final修飾的,以字面量的形式創建String變量時,jvm會在編譯期間就把該字面量(“hello”)放到字符串常量池中,由Java程序啟動的時候就已經加載到內存中了。這個字符串常量池的特點就是有且只有一份相同的字面量,如果有其它相同的字面量,jvm則返回這個字面量的引用,如果沒有相同的字面量,則在字符串常量池創建這個字面量並返回它的引用。由於s2指向的字面量“hello”在常量池中已經存在了(s1先於s2),於是jvm就返回這個字面量綁定的引用,所以s1==s2。s3中字面量的拼接其實就是“hello”,jvm在編譯期間就已經對它進行優化,所以s1和s3也是相等的。s4中的new String("lo")生成了兩個對象,"lo","new String("lo")","lo"存在字符串常量池,"new String("lo")"存在堆中,String s4 = "hel" + new String("lo")實質上是兩個對象的相加,編譯器不會進行優化,相加的結果存在堆中,而s1存在字符串常量池中,當然不相等。s1==s9的原理一樣。s4==s5兩個相加的結果都在堆中,不用說,肯定不相等。s1==s6中,s5.intern()方法能使一個位於堆中的字符串在運行期間動態地加入到字符串常量池中(字符串常量池的內容是程序啟動的時候就已經加載好了),如果字符串常量池中有該對象對應的字面量,則返回該字面量在字符串常量池中的引用,否則,創建復制一份該字面量到字符串常量池並返回它的引用。因此s1==s6輸出true。
第二段代碼:
jdk1.6下字符串常量池是在永久區中,是與堆完全獨立的兩個空間,s1指向堆中的內容,s2指向字符串常量池中的內容,兩者當然不一樣,s1.intern1()將字面量加入字符串常量池中,由於字符串常量池中已經存在該字面量,所以返回該字面量的唯一引用,intern1==s2就輸出true。
jdk1.7,1.8下字符串常量池已經轉移到堆中了,是堆中的一部分內容,jvm設計人員對intern()進行了一些修改,當執行s3.intern()時,jvm不再把s3對應的字面量復制一份到字符串常量池中,而是在字符串常量池中存儲一份s3的引用,這個引用指向堆中的字面量,當運行到String s4 = "hellohello"時,發現字符串常量池已經存在一個指向堆中該字面量的引用,則返回這個引用,而這個引用就是s3。所以s3==s4輸出true。
第三段代碼:
jdk1.6下輸出false,false,原理和上面一樣。
jdk1.7,1.8下輸出false,false,s3指向堆內容,接着s4="hellohello"發現字符串常量池中沒有該字面量,創建並返回該引用。s3,s4一個在堆,一個在字符串常量池,是兩個獨立的引用,所以s3==s4輸出false。
2018-03-17 05:44:23
