Java StringBuilder和StringBuffer原始碼分析

segmentfault發表於2016-01-08

簡介

StringBuilder 與 StringBuffer 是兩個常用的操作字串的類。大家都知道, StringBuilder 是執行緒不安全的,而 StringBuffer 是執行緒不安全的。前者是JDK1.5加入的,後者在JDK1.0就有了。下面分析一下它們的內部實現。

繼承關係

public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence

public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence

可以看到,兩個類的繼承關係是一模一樣的。 Serializable 是可以序列化的標誌。 CharSequence 介面包含了 charAt() 、 length() 、 subSequence() 、 toString() 等方法, String 類也實現了這個介面。這裡的重點是抽象類 AbstractStringBuilder ,這個類封裝了 StringBuilder 和 StringBuffer 大部分操作的實現。

AbstractStringBuilder

變數及構造方法

char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}

AbstractStringBuilder 內部用一個 char[] 陣列儲存字串,可以在構造的時候指定初始容量方法。

擴容

public void ensureCapacity(int minimumCapacity) {
    if (minimumCapacity > 0)
        ensureCapacityInternal(minimumCapacity);
}
 private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}

擴容的方法最終是由 expandCapacity() 實現的,在這個方法中首先把容量擴大為 原來的容量加2 ,如果此時仍小於指定的容量,那麼就把新的容量設為 minimumCapacity 。然後判斷是否溢位,如果溢位了,把容量設為 Integer.MAX_VALUE 。最後把 value 值進行拷貝, 這顯然是耗時操作

append()方法

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

append() 是最常用的方法,它有很多形式的過載。上面是其中一種,用於追加字串。如果 str 是 null ,則會呼叫 appendNull() 方法。這個方法其實是追加了 ‘n’ 、 ‘u’ 、 ‘l’ 、 ‘l’ 這幾個字元。如果不是 null ,則首先擴容,然後呼叫 String 的 getChars() 方法將 str 追加到 value 末尾。最後返回物件本身,所以 append() 可以連續呼叫。

StringBuilder

AbstractStringBuilder 已經實現了大部分需要的方法, StringBuilder 和 StringBuffer 只需要呼叫即可。下面來看看 StringBuilder 的實現。

構造器

public StringBuilder() {
    super(16);
}
public StringBuilder(int capacity) {
    super(capacity);
}
public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
}
public StringBuilder(CharSequence seq) {
    this(seq.length() + 16);
    append(seq);
}

可以看出, StringBuilder 預設的容量大小為16 。當然也可以指定初始容量,或者以一個已有的字元序列給 StringBuilder 物件賦初始值。

append()方法

public StringBuilder append(String str) {
    super.append(str);
    return this;
}
public StringBuilder append(CharSequence s) {
    super.append(s);
    return this;
}

append() 的過載方法很多,這裡隨便列舉了兩個。顯然,這裡是直接呼叫的父類 AbstractStringBuilder 中的方法。

toString()

 public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

toString() 方法返回了一個新的 String 物件,與原來的物件不共享記憶體。其實 AbstractStringBuilder 中的 subString() 方法也是如此。

SringBuffer

StiringBuffer 跟 StringBuilder 類似,只不過為了實現同步,很多方法使用l Synchronized 修飾,如下面的方法:

public synchronized int length() {
        return count;
}
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}
public synchronized void setLength(int newLength) {
    toStringCache = null;
    super.setLength(newLength);
}

可以看到,方法前面確實加了 Synchronized 。

另外,在上面的 append() 以及 setLength() 方法裡面還有個變數 toStringCache 。這個變數是用於最近一次 toString() 方法的快取,任何時候只要 StringBuffer 被修改了這個變數會被賦值為 null 。 StringBuffer 的 toString 如下:

public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}

在這個方法中,如果 toStringCache 為 null 則先快取。最終返回的 String 物件有點不同,這個構造方法還有個引數 true 。找到 String 的原始碼看一下:

 String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

原來這個構造方法構造出來的 String 物件並沒有實際複製字串,只是把 value 指向了構造引數,這是為了節省複製元素的時間。不過這個構造器是具有包訪問許可權,一般情況下是不能呼叫的。

總結

  • StringBuilder 和 StringBuffer 都是可變字串,前者執行緒不安全,後者執行緒安全。
  • StringBuilder 和 StringBuffer 的大部分方法均呼叫父類 AbstractStringBuilder 的實現。其擴容機制首先是把容量變為原來容量的2倍加2。最大容量是 Integer.MAX_VALUE ,也就是 0x7fffffff 。
  • StringBuilder 和 StringBuffer 的預設容量都是16,最好預先估計好字串的大小避免擴容帶來的時間消耗。

相關文章