-
String 特性
1.其定義的字符串序列不可變。
2.是一個final類,不可被繼承,且其內部一些重要方法被定義為final類型,不可重寫。
3.內部實現Serializable接口(支持字符串序列化)和Comparable接口(支持字符串比較大小)。
4.內部定義了final char [ ] value 用於存儲字符串數據。
-
String的實例化方式
1.字面量賦值的形式實例化:
String str1 = "abc"
2.用 new + 構造器形式實例化:
String str2 = new String("abc")
下面來分析一下兩種不同實例化方式的區別:
當我們執行System.out.println(s1 == s2);
的時候,輸出結果為``false`,
而執行System.out.println(s1.equals(s2));
的時候,輸出結果為true
,
這和虛擬機的內存分配有關:
對於str1字面量賦值的形式來說,字符串常量是存放在常量池中。而對於str2的構造器賦值形式,堆中的value存放的是new String("abc")
對象本身,而str2是棧中開辟的一個內存塊,他里面存放了指向對象本身的引用地址。有一點需要知道,在常量池中存放的東西都是唯一的,不會出現兩個相同的內容,這也是為了減少內存開銷和提升jvm的性能優化,所以在使用str2 的時候,對象本身又會到常量池中找是否有abc
,如果沒有則創建新的,如果有,則直接使用。
在之前的文章中也探究過==
和equals
的區別,當用==比較的時候,對於基本數據類型,比較的是內容,值是否相等。而對於剛剛的str1和str2,他們都是引用型數據類型,用==比較的時候,比較的是地址,很明顯,str1的地址直接指向常量池中的abc,而str2 的地址是指向堆內存中的實例對象,所以==比較肯定是false,而用equals比較的時候,結果為true,這是因為String類對object類的equals方法進行了重寫,object類中的equals方法底層用的還是==來判斷地址值。
總結區別:
1.字面量賦值的形式實例化,字符常量內容存於常量池,變量存於棧中,直接指向常量池。
2.new + 構造器形式實例化,會先在堆中創建實例對象,引用對象存於棧中,然后再去常量 區尋找需要的字符常量,如果找到了,直接使用,沒找到則開辟新的空間並存儲內容。
-
String的不可變性
我們都知道String是不可變的序列,那為什么不可變,又是怎么實現的呢?先來看一段源碼:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
可以看出,String是一個final
類,也就意味着他不可以被繼承,而且其內部成員變量是private
修飾,方法也是final
修飾,同樣也就意味着他的成員變量不會直接暴露給用戶,方法不可以被重寫。這樣實現了方法不可變(不能重寫),變量不能變,比如private final char value[];
這里就是string字符串不能變的實現。
字符串不可變底層實現分析:
當運行如下代碼的時候:
String s1 = "Java";
String s2 = "Hello";
String s5 = s1;
s5 = "change";
String s3 = new String("Hello");
String s4 = new String("Java");
字符串常量在虛擬機內存空間的情況如圖所示:
可見,對於String s1 = "Java"
這種字面量賦值的形式,會直接在常量池中開辟一個空間用於存儲相應的字符串(前提是常量池中還沒有該字符串),而String s3 = new String("Hello")
這樣的,會先在堆中創建對象,然后再去常量池中找是否有需要的字符常量,如果有,則直接使用,如果沒有,也同樣需要開辟新的空間來存儲。
重點看 :
String s1 = "Java";
String s5 = s1;
s5 = "change";
當執行String s5 = s1
時,s5會直接去使用s1在常量池中的內容,而后面當執行s5 = "change"
的時候,也就是說需要對Java這個字符串進行修改,可是這個字符串除了s5自己使用外,s1也在使用,所以就不能直接修改他,而是要在空間中重新開辟一個空間,用於存儲change
。這就是字符串不可以直接修改的底層實現!
字符串設置為不可變的原因:
①出於安全考慮,程序在運行之前虛擬機會把字符常量,靜態變量等預加載到常量池(方法區) 中存儲起來,在程序運行的時候直接調用,但是常量池里面的信息不會有重復的,每一個都是 唯一的(這樣是為了減少內存的開銷,提升性能),這些信息是線程共享的,同一個字符串可 能會被多個線程使用,如果字符串可變,當某個線程修對他做了修改,其他正在使用該字符串 的線程可能就會出現嚴重的錯誤,從而變得不安全。
②保證hash值不會經常變動,具有唯一性,使得類似HashMap的容器能實現key—value的功能
-
String 字符串的拼接
static String s1 = "Hello"; static String s2 = "Java"; static String s3 = "Hello"+"Java"; static String s4 = "HelloJava"; static String s5 = s1 + "Java"; static String s6 = "Hello" + s2; static String s7 = s1 + s2; static String s8 = (s1 + s2).intern();
內存分配如圖:
字符串拼接總結:
1.常量和常量的拼接,結果也在常量池中,且不存在兩個相同的常量。
2.只要參與拼接的項里面有變量,結果就在堆中。
3.使用(String).inter()
方法處理拼接后,被處理的字符串會進入常量池中。
-
說在最后
文章僅是筆者的個人理解,難免存在許多不完善和理解不恰當之處,歡迎批評指正。
碼字不易,創作辛苦,歡迎轉載分享,請注明出處。
交流歡迎Q我:321662487