在研究String直接賦值與new String的區別之前我們需要先了解java中的字符串常量池的概念
字符串常量池
String類是我們平常項目中使用頻率非常高的一種對象類型,jvm為了提升性能和減少內存開銷,避免字符的重復創建,其維護了一塊特殊的內存空間,即字符串池,當需要使用字符串時,先去字符串池中查看該字符串是否已經存在,如果存在,則可以直接使用,如果不存在,初始化,並將該字符串放入字符創常量池中。
使用String直接賦值
String str = “abc”;可能創建一個或者不創建對象,如果”abc”在字符串池中不存在,會在java字符串池中創建一個String對象(”abc”),然后str指向這個內存地址,無論以后用這種方式創建多少個值為”abc”的字符串對象,始終只有一個內存地址被分配。==判斷的是對象的內存地址,而equals判斷的是對象內容。通過以下代碼測試:
String str = "abc"; String str1 = "abc"; String str2 = "abc"; System.out.println(str==str1);//true System.out.println(str==str2);//true
也就是str、str1、str2都是指向同一個內存地址。
使用new String 創建字符串
String str = new String(“abc”);至少會創建一個對象,也有可能創建兩個。因為用到new關鍵字,肯定會在堆中創建一個String對象,如果字符池中已經存在”abc”,則不會在字符串池中創建一個String對象,如果不存在,則會在字符串常量池中也創建一個對象。
String str = new String("abc"); String str1 = new String("abc"); String str2 = new String("abc"); System.out.println(str==str1);//false System.out.println(str==str2);//false
可以看出來,str、str1、str2指向的是不同的內存地址
這里解釋一下,對於通過 new 產生一個字符串(假設為 ”china” )時,會先去常量池中查找是否已經有了 ”china” 對象,如果沒有則在常量池中創建一個此字符串對象,然后堆中再創建一個常量池中此 ”china” 對象的拷貝對象。
使用String拼接字符串
項目中除了直接使用=賦值,也會用到字符串拼接,字符串拼接又分為變量拼接和已知字符串拼接
String str = "abc";//在常量池中創建abc String str1 = "abcd";//在常量池中創建abcd String str2 = str+"d";//拼接字符串,此時會在堆中新建一個abcd的對象,因為str2編譯之前是未知的 String str3 = "abc"+"d";//拼接之后str3還是abcd,所以還是會指向字符串常量池的內存地址 System.out.println(str1==str2);//false System.out.println(str1==str3);//true
str和str1都是字符串常量所以在編譯期就被確定了!而str2中有個str是引用不是字符串常量所以不會在編譯期確定。
而String是final的!所以在b+"c"的時候實際上是新創建了一個對象,然后在把新創建對象的引用傳給c.
所以在項目中還是不要使用new String去創建字符串,最好使用String直接賦值。
String 有一個intern() 方法,native,用來檢測在String pool是否已經有這個String存在。
public String intern()
返回字符串對象的規范化表示形式。
一個初始時為空的字符串池,它由類 String 私有地維護。
當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字符串(該對象由 equals(Object) 方法確定),則返回池中的字符串。否則,將此 String 對象添加到池中,並且返回此 String 對象的引用。
它遵循對於任何兩個字符串 s 和 t,當且僅當 s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true。
所有字面值字符串和字符串賦值常量表達式都是內部的。
返回:
一個字符串,內容與此字符串相同,但它保證來自字符串池中。
考慮下面的問題:
public static void main(String[] args) throws Exception { String a = "b" ; String b = "b" ; System.out.println( a == b); //true String d = new String( "d" ).intern() ; String c = "d" ; System.out.println( c == d); //true System.out.println("------------------"); String d1 = new String( "d" ) ; String e1=d1.intern(); String c1 = "d" ; System.out.println( c1 == d1); //false System.out.println( c1 == e1); //true System.out.println( e1 == d1); //false System.out.println("------------------"); String s1=new String("kvill"); String s2=s1.intern(); System.out.println( s1==s2 ); //false System.out.println( s1+" "+s2 ); //kvill kvill System.out.println( s2==s1.intern() ); //true }
面試題:
String s = new String(“xyz”); 產生幾個對象?
一個或兩個。如果常量池中原來沒有 ”xyz”, 就是兩個。如果原來的常量池中存在“xyz”時,就是一個。對於基礎類型的變量和常量:變量和引用存儲在棧中,常量存儲在常量池中。
java中String new和直接賦值的區別
對於字符串:其對象的引用都是存儲在棧中的,如果是編譯期已經創建好(直接用雙引號定義的)的就存儲在常量池中,如果是運行期(new出來的)才能確定的就存儲在堆中。對於equals相等的字符串,在常量池中永遠只有一份,在堆中有多份。