前陣子和同事在吃飯時聊起Java的String,以爲本身以前的筆記寫的略顯零散。故此又從新整理了一下。java
String在Java中算是一個有意思的類型,是final類型,所以不能夠繼承這個類、不能修改這個類。設計模式
咱們先來看一段代碼:緩存
String s = "Hello"; s = s + " world!";
試問:這兩行代碼執行後,原始的 String 對象中的內容到底變了沒有?安全
答案是沒有。由於 String 被設計成不可變(immutable)類,因此它的全部對象都是不可變對象。在 這段代碼中,s 原先指向一個 String 對象,內容是 "Hello",而後咱們對 s 進行了+操做。這時,s 不指向原來那個對象了, 而指向了另外一個 String 對象,內容爲"Hello world!",原來那個對象還存在於內存之中,只 是 s 這個引用變量再也不指向它了。性能優化
經過上面的說明,咱們很容易導出另外一個結論,若是常常對字符串進行各類各樣的修改,或 者說,不可預見的修改,那麼使用 String 來表明字符串的話會引發很大的內存開銷。由於 String 對象創建以後不能再改變,因此對於每個不一樣的字符串,都須要一個 String 對象來 表示。這時,應該考慮使用 StringBuffer類,它容許修改,而不是每一個不一樣的字符串都要生 成一個新的對象。而且,這兩種類的對象轉換十分容易。app
同時,咱們還能夠知道,若是要使用內容相同的字符串,沒必要每次都 new 一個 String。例 如咱們要在構造器中對一個名叫 s 的 String 引用變量進行初始化,把它設置爲初始值,應當這樣作:性能
public class Demo { private String s; ... public Demo { s = "Initial Value"; } ... //而非 s = new String("Initial Value"); }
前者每次都會調用構造器,生成新對象,性能低下且內存開銷大,而且沒有意義,由於 String 對象不可改變,因此對於內容相同的字符串,只要一個 String 對象來表示就能夠了。也就 說,屢次調用上面的構造器建立多個對象,他們的 String 類型屬性 s 都指向同一個對象。 上面的結論還基於這樣一個事實:對於字符串常量,若是內容相同,Java 認爲它們表明同 一個 String 對象。而用關鍵字 new 調用構造器,老是會建立一個新的對象,不管內容是否 相同。優化
再請你們看一段代碼:ui
String s = new String("xyz");
問題:建立了幾個 String Object?兩者之間有什麼區別?spa
一個或兩個 。
在Java中,其實有不少常量池相關的概念:
相似這種常量池的思想即涉及到了一個設計模式——享元模式。顧名思義,共享元素。
也就是說:一個系統中若是有多處用到了相同的一個元素,那麼咱們應該只存儲一份此元素,而讓全部地方都引用這一個元素
那麼爲何要不可變呢?
String被許多的Java類庫用來當作參數。例:
倘若String不是固定不變的,將會引發各類安全隱患。
在前面提到過常量池的享元模式。這樣在拷貝或者建立內容相同對象時,就沒必要複製對象自己,而是隻要引用它便可。這樣的開銷比起copy object是天差地別的。另外,也就只有不可變對象才能使用常量池,由於能夠保證引用同一常量值的多個變量不產生相互影響。
一樣也是因爲String對象的不可變特性,因此String對象能夠自身緩存HashCode。Java中String對象的哈希碼被頻繁地使用, 好比在hashMap 等容器中。字符串不變性保證了hash碼的惟一性,所以能夠放心地進行緩存。這也是一種性能優化手段,意味着沒必要每次都去計算新的哈希碼:
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
JAVA 平臺提供了兩個類:String 和 StringBuffer,它們能夠儲存和操做字符串,即包含多個字符的字符數據。這個 String 類提供了數值不可改變的字符串。而這個 StringBuffer 類提供 的字符串進行修改。當你知道字符數據要改變的時候你就可使用 StringBuffer。典型地, 你可使用 StringBuffers 來動態構造字符數據。另外,String 實現了 equals 方法,new String(「abc」).equals(newString(「abc」)的結果爲true,而StringBuffer沒有實現equals方法, 因此,new StringBuffer(「abc」).equals(newStringBuffer(「abc」)的結果爲 false。
接着要舉一個具體的例子來講明,咱們要把1到100的全部數字拼起來,組成一個串。
StringBuffer sbf = new StringBuffer(); for(int i=0;i<100;i++){ sbf.append(i); }
上面的代碼效率很高,由於只建立了一個 StringBuffer 對象,而下面的代碼效率很低,由於 建立了101個對象。
String str = new String(); for(int i=0;i<100;i++) { str = str + i; }
在講二者區別時,應把循環的次數搞成10000,而後用 endTime-beginTime 來比較二者執 行的時間差別。
String 覆蓋了 equals 方法和 hashCode 方法,而 StringBuffer沒有覆蓋 equals 方法和 hashCode 方法,因此,將 StringBuffer對象存儲進 Java集合類中時會出現問題。
StringBuilder不是線程安全的,可是單線程中中的性能比StringBuffer高。
String str1 = "abcd"; String str2 = new String("abcd"); System.out.println(str1==str2);//false
這兩種不一樣的建立方法是有差異的:
String str1 = "str"; String str2 = "ing"; String str3 = "str" + "ing"; String str4 = str1 + str2; System.out.println(str3 == str4);//false String str5 = "string"; System.out.println(str3 == str5);//true
public static final String str1 = "ab"; public static final String str2 = "cd"; public static void main(String[] args) { String s = str1 + str2; // 將兩個常量用+鏈接對s進行初始化 String t = "abcd"; if (s == t) { System.out.println("s等於t,它們是同一個對象"); } else { System.out.println("s不等於t,它們不是同一個對象"); } }
public static final String str1; public static final String str2; static { str1 = "ab"; str2 = "cd"; } public static void main(String[] args) { // 將兩個常量用+鏈接對s進行初始化 String s = str1 + str2; String t = "abcd"; if (s == t) { System.out.println("s等於t,它們是同一個對象"); } else { System.out.println("s不等於t,它們不是同一個對象"); } }
運行時常量池相對於Class文件常量池的另一個重要特徵是具有動態性,Java語言並不要求常量必定只有編譯期才能產生,也就是並不是預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的就是String類的intern()方法。
String的intern()方法會查找在常量池中是否存在一份equal相等的字符串,若是有則返回該字符串的引用,若是沒有則添加本身的字符串進入常量池。
public static void main(String[] args) { String s1 = new String("計算機"); String s2 = s1.intern(); String s3 = "計算機"; System.out.println("s1 == s2? " + (s1 == s2)); System.out.println("s3 == s2? " + (s3 == s2)); } //s1 == s2? false //s3 == s2? true
public class Test { public static void main(String[] args) { String hello = "Hello", lo = "lo"; System.out.println((hello == "Hello") + " "); System.out.println((Other.hello == hello) + " "); System.out.println((other.Other.hello == hello) + " "); System.out.println((hello == ("Hel" + "lo")) + " "); System.out.println((hello == ("Hel" + lo)) + " "); System.out.println(hello == ("Hel" + lo).intern()); } }
class Other { static String hello = "Hello"; }
package other; public class Other { public static String hello = "Hello"; } //true true true true false true