String,StringBuilder,StringBuffer 實現原理解析


String,StringBuilder,StringBuffer 實現原理解析

定義:

從 jdk1.5 開始提供的新的封裝字符串的類StringBuilder,其字符串拼接操作的效率遠遠高於 String

Java 里面提供了 String,StringBuffer 和 StringBuilder 三個類來封裝字符串

簡介:

我們知道字符串其實就是由若干個字符線性排列而成的,可以理解為字符數組 Array,那么既然是數組實現的,那就需要考慮到數組的特性,數組在內存中是一塊連續的地址空間塊,即在定義數組的時候需要指定數組的大小

換言之, 數組就分為可變數組和不可變數組。可變數組能夠動態插入和刪除(例如StringBuffer和StringBuilder),而不可變數組一旦分配好空間后則不能進行動態插入或刪除操作(例如String)。

在實際的字符串應用場景中,涉及到多種操作,比如字符串的插入,刪除,修改,拼接,查詢,替換...

String:

不可變類*,屬性 value 為不可變數組*,即 String 初始化構造器沒有初始容量為 16 的概念,你定義多少,String 中字符數組的長度就是多少,不存在字符數組擴容一說。看下源碼:

img

2

img

3

final 修飾的 String 類,以及 final 修飾的 char[] value, 表示 String 類不可被繼承,且 value 只能被初始化一次。這里的 value 變量其實就是存儲了 String 字符串中的所有字符。

那既然 String,不可變。我們再看下它的*截取方法 subString()實現*

img

4

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

img

5

img

6

類似的我們可以看到,String 類的 concat 方法,replace 方法,都是內部重新生成一個 String 對象的。

這也就是為什么我們如果采用String 對象頻繁的進行拼接,截取,替換操作效率很低下的原因。

下面再看下 StringBuilder 對象的源碼,分析為何其在做字符串的拼接,截取,替換方面效率遠遠高於 String

StringBuilder:

內部可變數組*,存在初始化 StringBuilder 對象中字符數組容量為 16,存在擴容*

img

7

StringBuilder 類繼承 AbstractStringBuilder 抽象類,其中 StringBuilder 的大部分方法都是直接調用的父類的實現。

首先看下 StringBuilder 的構造方法

img

8

1:空參數的構造方法

img

9

img

10

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

img

11

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

img

12

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

img

13

具體看下父類 AbstractStringBuilder 的 append 方法

img

14

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

img

15

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

img

16

2:如果不為 null 的話,就獲取這次需要 append 的 str 的字符長度。緊接着執行是否需要擴容的方法

3:重點看下 append 方法的關鍵:String 的getChars 方法(從 str 的 0 位開始,到 str 的長度,當前 StringBuilder 對象的字符數組,當前數組已有的字符長度)

img

17

img

18

其實是調用了 System 的 arraycopy 方法 參數如下:

*value* 為 str 的內部不可變字符數組,

srcBegin 為從 str 字符串數組的 0 下標開始,

*srcEnd* 為 str 字符串數組的長度,

dst 為 StringBuilder 對象的內部可變字符數組,

dstBegin 則為 StringBuilder 對象中已有的字符長度(char[] 已有的元素長度)

即整個 StringBuilder 的 append 方法,本質上是調用 System 的 native 方法,直接將 String 類型的 str 字符串中的字符數組,拷貝到了 StringBuilder 的字符數組中

img

19

toString():

最后說下 StringBuilder 的 toString 方法,

img

這里的toString 方法直接 new 一個 String 對象,將 StringBuilder 對象的 value 進行一個拷貝,重新生成一個對象,不共享之前 StringBuilder 的 char[]

以上就是 StringBuilder 的拼接字符串的原理分析,可以發現沒有像 String 一樣去重新 new 對象,所以在頻繁的拼接字符上,StringBuilder 的效率遠遠高於 String 類。

StringBuffer:

線程安全的高效字符串操作類,看下源碼:

img

19

類圖和 StringBuilder 一樣,不多說

構造函數:

img

20

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

img

21

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

這里比 StringBuilder 多了一個參數

img

22

這里的作用簡單介紹一下,就是去緩存 toString 的

可以看下 StringBuffer 的 toString 方法

img

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 該屬性值。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM