String,StringBuilder,StringBuffer 實現原理解析
定義:
從 jdk1.5 開始提供的新的封裝字符串的類StringBuilder,其字符串拼接操作的效率遠遠高於 String
Java 里面提供了 String,StringBuffer 和 StringBuilder 三個類來封裝字符串
簡介:
我們知道字符串其實就是由若干個字符線性排列而成的,可以理解為字符數組 Array,那么既然是數組實現的,那就需要考慮到數組的特性,數組在內存中是一塊連續的地址空間塊,即在定義數組的時候需要指定數組的大小
換言之, 數組就分為可變數組和不可變數組。可變數組能夠動態插入和刪除(例如StringBuffer和StringBuilder),而不可變數組一旦分配好空間后則不能進行動態插入或刪除操作(例如String)。
在實際的字符串應用場景中,涉及到多種操作,比如字符串的插入,刪除,修改,拼接,查詢,替換...
String:
不可變類*,屬性 value 為不可變數組*,即 String 初始化構造器沒有初始容量為 16 的概念,你定義多少,String 中字符數組的長度就是多少,不存在字符數組擴容一說。看下源碼:

2

3
final 修飾的 String 類,以及 final 修飾的 char[] value, 表示 String 類不可被繼承,且 value 只能被初始化一次。這里的 value 變量其實就是存儲了 String 字符串中的所有字符。
那既然 String,不可變。我們再看下它的*截取方法 subString()實現*

4
這里可以看到,在 substring 方法中,如果傳入的參數為 0,就返回自身原對象,否則就是重新創建一個新的對象。

5

6
類似的我們可以看到,String 類的 concat 方法,replace 方法,都是內部重新生成一個 String 對象的。
這也就是為什么我們如果采用String 對象頻繁的進行拼接,截取,替換操作效率很低下的原因。
下面再看下 StringBuilder 對象的源碼,分析為何其在做字符串的拼接,截取,替換方面效率遠遠高於 String
StringBuilder:
內部可變數組*,存在初始化 StringBuilder 對象中字符數組容量為 16,存在擴容*。

7
StringBuilder 類繼承 AbstractStringBuilder 抽象類,其中 StringBuilder 的大部分方法都是直接調用的父類的實現。
首先看下 StringBuilder 的構造方法

8
1:空參數的構造方法

9

10
2:自定義初始容量 - 構造函數

11
3:以字符串 String 作為參數的構造

12
在參數 Str 數組長度的基礎上再增加 16 個字符長度,作為 StringBuilder 實例的初始數組容量(給將來的拼接和增加留出余量),並將 str 字符串 append 到 StringBuilder 的數組中。

13
具體看下父類 AbstractStringBuilder 的 append 方法

14
1:首先判斷 append 的參數是否為 null,如果為 null 的話,這里也是可以 append 進去的

15
其中 ensureCapacityInternal 方法是確保這次 append 的時候 StringBuilder 的內部數組容量是滿足的,即這次要 append 的 null 字符長度為 4,加上之前內部數組中已有的字符位數 c 之后作為參數執行。

16
2:如果不為 null 的話,就獲取這次需要 append 的 str 的字符長度。緊接着執行是否需要擴容的方法
3:重點看下 append 方法的關鍵:String 的getChars 方法(從 str 的 0 位開始,到 str 的長度,當前 StringBuilder 對象的字符數組,當前數組已有的字符長度)

17

18
其實是調用了 System 的 arraycopy 方法 參數如下:
*value* 為 str 的內部不可變字符數組,
srcBegin 為從 str 字符串數組的 0 下標開始,
*srcEnd* 為 str 字符串數組的長度,
dst 為 StringBuilder 對象的內部可變字符數組,
dstBegin 則為 StringBuilder 對象中已有的字符長度(char[] 已有的元素長度)
即整個 StringBuilder 的 append 方法,本質上是調用 System 的 native 方法,直接將 String 類型的 str 字符串中的字符數組,拷貝到了 StringBuilder 的字符數組中

19
toString():
最后說下 StringBuilder 的 toString 方法,

這里的toString 方法直接 new 一個 String 對象,將 StringBuilder 對象的 value 進行一個拷貝,重新生成一個對象,不共享之前 StringBuilder 的 char[]
以上就是 StringBuilder 的拼接字符串的原理分析,可以發現沒有像 String 一樣去重新 new 對象,所以在頻繁的拼接字符上,StringBuilder 的效率遠遠高於 String 類。
StringBuffer:
線程安全的高效字符串操作類,看下源碼:

19
類圖和 StringBuilder 一樣,不多說
構造函數:

20
和 StringBuilder 一樣,也不用多說,重點看下其 append 方法:

21
可以看到這里就是在 append 方法上加了同步鎖,來實現多線程下的線程安全。其他的和 StringBuilder 一致。
這里比 StringBuilder 多了一個參數

22
這里的作用簡單介紹一下,就是去緩存 toString 的
可以看下 StringBuffer 的 toString 方法

23
這里的作用就是如果 StringBuffer 對象此時存在 toStringCache,在多次調用其 toString 方法時,其 new 出來的 String 對象是會共享同一個 char[] 內存的,達到共享的目的。但是 StringBuffer 只要做了修改,其toStringCache 屬性值都會置 null 處理。這也是 StringBuffer 和 StringBuilder 的一個區別點。
因為在這些方法上都加上了synchronized,所以同時只能有一個線程讀(toString)或者修改,保證了線程安全,但是顯然使用同一把鎖效率是很低的;而toStringCache的設計也保證了在不修改的前提下各個線程toString()創建的字符串都依賴同一個對象,減少了不必要的對象的創建
注意,上面的方法在JDK11中已經發生了改變,現在的toStringCache是String類型的:
@HotSpotIntrinsicCandidate
public synchronized String toString() {
return this.toStringCache == null ? (this.toStringCache = this.isLatin1() ? StringLatin1.newString(this.value, 0, this.count) : StringUTF16.newString(this.value, 0, this.count)) : new String(this.toStringCache);
}
而對應的String的構造函數是(原圖中的傳入char[]和boolean類型的構造函數已經不存在了):
@HotSpotIntrinsicCandidate
public String(String original) {
//private final byte[] value;
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}
可以看到,也是直接使用了original的value數組
總結:
String 類不可變,內部維護的 char[] 數組長度不可變,為 final 修飾,String 類也是 final 修飾,不存在擴容。字符串拼接,截取,都會生成一個新的對象。頻繁操作字符串效率低下,因為每次都會生成新的對象。
StringBuilder 類內部維護可變長度 char[] , 初始化數組容量為 16,存在擴容, 其 append 拼接字符串方法內部調用 System 的 native 方法,進行數組的拷貝,不會重新生成新的 StringBuilder 對象。非線程安全的字符串操作類, 其每次調用 toString 方法而重新生成的 String 對象,不會共享 StringBuilder 對象內部的 char[],會進行一次 char[] 的 copy 操作。
StringBuffer 類內部維護可變長度 char[], 基本上與 StringBuilder 一致,但其為線程安全的字符串操作類,大部分方法都采用了 Synchronized 關鍵字修改,以此來實現在多線程下的操作字符串的安全性。其 toString 方法而重新生成的 String 對象,會共享 StringBuffer 對象中的 toStringCache 屬性(char[]),但是每次的 StringBuffer 對象修改,都會置 null 該屬性值。
