[TOC]java
字符串就是一連串的字符序列,Java提供了String、StringBuilder、StringBuffer三個類來封裝字符串
String
類是不可變類,String對象被建立之後,對象中的字符序列是不可改變的,直到這個對象被銷燬數組
jdk1.8 public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; //jdk1.9中將char數組替換爲byte數組,緊湊字符串帶來的優點:更小的內存佔用,更快的操做速度。 //構造函數 public String(String original) { this.value = original.value; this.hash = original.hash; } //構造函數 public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } //返回一個新的char[] public char[] toCharArray() { // Cannot use Arrays.copyOf because of class initialization order issues char result[] = new char[value.length]; System.arraycopy(value, 0, result, 0, value.length); return result; } }
根據上面的代碼,咱們看看String到底是怎麼保證不可變的。緩存
value
的接口value
被final修飾,因此變量的引用不可變。char[]·
爲引用類型仍能夠經過引用修改實例對象,爲此String(char value[])
構造函數內部使用的copyOf
而不是直接將value[]
複製給內部變量`。arraycopy()
的方式返回一個新的char[]
String
類中的函數也到處透露着不可變的味道,好比:replace()
public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { //從新建立新的char[],不改變原有對象中的值 char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } //最後返回新建立的String對象 return new String(buf, true); } } return this; }
固然不可變也不是絕對的,仍是能夠經過反射獲取到變value引用,而後經過value[]修改數組的方式改變value對象實例安全
String a = "Hello World!"; String b = new String("Hello World!"); String c = "Hello World!"; //經過反射修改字符串引用的value數組 Field field = a.getClass().getDeclaredField("value"); field.setAccessible(true); char[] value = (char[]) field.get(a); System.out.println(value);//Hello World! value[5] = '&'; System.out.println(value);//Hello&World! // 驗證b、c是否被改變 System.out.println(b);//Hello&World! System.out.println(c);//Hello&World!
寫到這裏該如何引出不可變的好處呢?忘記反射吧,咱們聊聊不可變的好處吧網絡
同一個字符串實例能夠被多個線程共享。函數
好比,網絡通訊的IP地址,類加載器會根據一個類的徹底限定名來讀取此類諸如此類,不可變性提供了安全性。源碼分析
具統計,常見應用使用的字符串中有大約一半是重複的,爲了不建立重複字符串,下降內存消耗和對象建立時的開銷。JVM提供了字符串緩存的功能——字符串常量池。若是字符串是可變的,咱們就能夠經過引用改變常量池總的同一個內存空間的值,其餘指向此空間的引用也會發生改變。性能
由於字符串是不可變的,因此在它建立的時候hashcode就被緩存了,不須要從新計算。這就使得字符串很適合做爲Map中的鍵,字符串的處理速度要快過其它的鍵對象。這就是HashMap中的鍵每每都使用字符串。ui
因爲它的不可變性,像字符串拼接、裁剪等廣泛性的操做,每每對應用性能有明顯影響。this
爲了解決這個問題,java爲咱們提供了兩種解決方案
仍是剛纔反射的示例
String a = "Hello World!"; String b = new String("Hello World!"); String c = "Hello World!"; //判斷字符串變量是否指向同一塊內存 System.out.println(a == b); System.out.println(a == c); System.out.println(b == c); // 經過反射觀察a, b, c 三者中變量value數組的真實位置 Field a_field = a.getClass().getDeclaredField("value"); a_field.setAccessible(true); System.out.println(a_field.get(a)); Field b_field = b.getClass().getDeclaredField("value"); b_field.setAccessible(true); System.out.println(b_field.get(b)); Field c_field = c.getClass().getDeclaredField("value"); c_field.setAccessible(true); System.out.println(c_field.get(c)); //經過反射發現String對象中變量value指向了同一塊內存
輸出
false true false [C@6f94fa3e [C@6f94fa3e [C@6f94fa3e
字符串常量的建立過程:
char["Hello World!".length()]
數組對象,而後在常量池中建立一個字符串對象並用數組對象初始化字符串對象的成員變量value,而後將這個字符串的引用返回,好比賦值給a因而可知,a和c對象指向常量池中相同的內存空間不言自明。
而b對象的建立是創建在以上的建立過程的基礎之上的。"Hello World!"
常量建立完成時返回的引用,會通過String
的構造函數。
public String(String original) { this.value = original.value; this.hash = original.hash; }
構造函數內部將引用的對象成員變量value
賦值給了內部成員變量value
,而後將新建立的字符創對象引用賦值給了b,這個過程發生在堆中。
再來感覺下下面這兩行代碼有什麼區別
String b = new String(a); String b = new String("Hello World!");
爲了彌補String的缺陷,Java前後提供了StringBuffer和StringBuilder可變字符串類。
兩者都繼承至AbstractStringBuilder,AbstractStringBuilder使用了char[] value
字符數組
abstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value; AbstractStringBuilder(int capacity) { value = new char[capacity]; } }
能夠看出AbstractStringBuilder類和其成員變量value都沒有使用final關鍵字。
StringBuilder和StringBuffer的value數組默認初始長度是16
public StringBuilder() { super(16); } public StringBuffer() { super(16); }
若是咱們拼接的字符串長度大概是能夠預計的,那麼最好指定合適的capacity,避免屢次擴容的開銷。
擴容產生多重開銷:拋棄原有數組,建立新的數組,進行arrycopy。
StringBuilder是非線程安全的,StringBuffer是線程安全的。
StringBuffer類中的方法使用了synchronized
同步鎖來保證線程安全。
關於鎖的話題很是大,會單獨成文來講明,這裏推薦一篇不錯的博客,有興趣的能夠看看