問題:
- String是如何保證不可變的?
- String為什麼要設計為不可變的?
- StringBuffer為什麼可變?底層邏輯是什麼?
- StringBuffer是如何進行擴容的?
String是如何保證不可變的?
String類被final修飾,不能被繼承。底層采用一個 private final char value[] 數組進行存儲,并且使用final修飾,是以String是不可變的。
- final修飾的不能被繼承;
- final修飾的變量指派後不能改變;
- final修飾的方法不能重寫;
但是這裡要明确一點,字元數組被 final 修飾,不能改變的是變量的引用位址,并不是字元數組的中的元素。可以對字元數組中的元素進行改變。
public static void main(String[] args) throws Exception {
final char[] chars = new char[]{'A', 'B', 'C'};
chars[0] = 'D';
System.out.println(chars);
}
既然字元數組是可變的,那為什麼String又是不可變的呢?其實更多的是在底層實作的。通過源碼可以發現 char 數組是 private 修飾的,并且沒有對外部提供任何修改 char 數組的方法。String被 final 修飾不可被繼承,也無法通過繼承重寫方法的方式進行破壞。通過這三種手段保證了 String 的不可變性。
String 為什麼設計為不可變的?
更容易實作字元串池
字元串池是用來存儲Java常量的一塊空間。主要用來緩存和重用字元串對象,進而提高性能和節省記憶體。當我們聲明一個字元串常量時,會先檢查字元串池是否存在相同内容的字元串常量。如果存在,則直接傳回字元串池中的對象引用;如果不存在,則在字元串池中建立一個新的字元串對象,并傳回對象的引用。
保證多線程安全
并發場景下,多個線程讀取同一個資源并不會引發線程問題的。但是多個線程對同一資源進行寫操作是不安全的,是以 String 通過它的不可變性,進而保證了多線程的安全問題。
避免安全問題
在網絡連接配接和資料庫連接配接中經常使用字元串作為參數,例如,網絡連接配接位址URL,反射機制所需要的 String 參數,其不變性可以保證連接配接的安全性。如果字元串是可變的,那麼可以通過修改字元串指向對象的值進行破壞,可能會引起嚴重的安全問題。
加快字元處理速度
因為 String 不可變,保證了 hashcode 的唯一性,那麼在建立對象時就可以将 hashcode 緩存,不需要重新參與計算。這也是 Map 喜歡使用 String 作為 Key 的原因,處理速度比其他的鍵對象要快。是以 HashMap 經常選用 String 作為鍵。
StringBuilder:
非線程安全,StringBuilder底層采用一個可變的 char[] 數組。也就是說StringBuilder底層這個 char 數組是可以進行擴容的。
改變字元串的底層邏輯:
StringBuilder 使用了 append() 進行了字元串的拼接,底層是使用了數組拷貝的方式進行資料的指派,将要拼接的字元數組 copy 到原字元數組中去,并且會在 copy 之前校驗原數組是否需要擴容。
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
// 檢驗是否需要擴容
ensureCapacityInternal(count + len);
// 數組copy
str.getChars(0, len, value, count);
count += len;
return this;
}
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
StringBuilder擴容:
StringBuilder在初始化的時候,預設是char數組長度是16,每次擴容是原char數組長度的是 2n+2。
StringBuffer:
線程安全,相比 StringBuilder 來說在原來的方法上使用了 synchronized 來保證線程安全。
性能比較:
StringBuilder > StringBuffer > String
總結:
如果遇到大量的字元串拼接的時候,優先選擇使用StringBuilder或StringBuffer。 String是字元串常量,不可變,使用String進行字元串操作的時候,每次都會建立一個新的對象,原來的對象就會變成垃圾被GC回收掉,很影響效率。 StringBuilder和StringBuffer它們是字元串變量,是可變的。當對字元串進行操作時,實際上實在對象上操作,并不會像String一樣每次建立新的對象。