1.其定義的字符串序列不可變。java
2.是一個final類,不可被繼承,且其內部一些重要方法被定義爲final類型,不可重寫。安全
3.內部實現Serializable接口(支持字符串序列化)和Comparable接口(支持字符串比較大小)。性能優化
4.內部定義了final char [ ] value 用於存儲字符串數據。jvm
1.字面量賦值的形式實例化: String str1 = "abc" 2.用 new + 構造器形式實例化: String str2 = new String("abc")
下面來分析一下兩種不一樣實例化方式的區別:性能
當咱們執行System.out.println(s1 == s2);
的時候,輸出結果爲`false
,優化
而執行System.out.println(s1.equals(s2));
的時候,輸出結果爲true
,線程
這和虛擬機的內存分配有關:
code
對於str1字面量賦值的形式來講,字符串常量是存放在常量池中。而對於str2的構造器賦值形式,堆中的value存放的是new String("abc")
對象自己,而str2是棧中開闢的一個內存塊,他裏面存放了指向對象自己的引用地址。有一點須要知道,在常量池中存放的東西都是惟一的,不會出現兩個相同的內容,這也是爲了減小內存開銷和提高jvm的性能優化,因此在使用str2 的時候,對象自己又會到常量池中找是否有abc
,若是沒有則建立新的,若是有,則直接使用。對象
在以前的文章中也探究過==
和equals
的區別,當用==比較的時候,對於基本數據類型,比較的是內容,值是否相等。而對於剛剛的str1和str2,他們都是引用型數據類型,用==比較的時候,比較的是地址,很明顯,str1的地址直接指向常量池中的abc,而str2 的地址是指向堆內存中的實例對象,因此==比較確定是false,而用equals比較的時候,結果爲true,這是由於String類對object類的equals方法進行了重寫,object類中的equals方法底層用的仍是==來判斷地址值。blog
總結區別:
1.字面量賦值的形式實例化,字符常量內容存於常量池,變量存於棧中,直接指向常量池。
2.new + 構造器形式實例化,會先在堆中建立實例對象,引用對象存於棧中,而後再去常量 區尋找須要的字符常量,若是找到了,直接使用,沒找到則開闢新的空間並存儲內容。
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的功能
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