1. 程式人生 > 實用技巧 >StringBuffer 和 StringBuilder

StringBuffer 和 StringBuilder

基於 JDK 1.8.0_151

StringBuilder 和 StringBuffer 均繼承自 AbstractStringBuilder,而 StringBuilder 在 StringBuffer 之後出現。按照順序逐個進行分析 AbstractStringBuilder,StringBuffer,StringBuilder 。

AbstractStringBuilder

AbstractStringBuilder 構造器

AbstractStringBuilder 的無參構造器的作用是為了讓子類能夠序列化和反序列化。另一個有參建構函式傳入的 capacity,例項化儲存字元序列的字元陣列 value

    AbstractStringBuilder() {
    }

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

append() 方法

append(String str) 的操作如下:

  • 判斷 str 是否為空,若為空,則直接呼叫 appendNull() 並返回;
  • 計算(count + len)追加 str 之後的長度,並確保儲存字元序列的字元陣列足夠長;
  • str.getChars() 方法將 str 複製到字元陣列 value(儲存了 StringBuffer 字元序列);
  • 返回當前物件。

ensureCapacityInternal() 方法會檢查字元陣列 value 的容量是否足以儲存追加之後的字元序列,不足則會進行擴容。count 表示 value 中下一個可用位置的下標。

    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;
    }

appendNull()

appendNull() 是一個私有方法,它同樣先確保 value 容量足夠,然後追加 'n','u','l','l' 四個字元到 value 陣列中。

AbstractStringBuilder.java 程式碼片段

    private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }

其它 append 的過載方法追加內容流程類似:

  • 判斷引數是否為空;
  • 確保 value 容量足夠;
  • 執行追加操作;
  • 返回當前物件。

ensureCapacity(int minimumCapacity) 和 ensureCapacityInternal(int minimumCapacity)

ensureCapacity(int minimumCapacity) 由 public 修飾,而 ensureCapacityInternal(int minimumCapacity) 由 private 修飾,前者呼叫了後者。前者引數只有輸入的 minimumCapacity 為正數時才有效。

    public void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > 0)
            ensureCapacityInternal(minimumCapacity);
    }

ensureCapacityInternal(int minimumCapacity) 先判斷 `minnumCapacity 是否大於字元陣列 value 的長度,若超過,則呼叫 newCapacity(int minCapacity) 計算新的長度,再 Arrays.copyOf() 方法對陣列進行了複製。Arrays.copyOf(char[] original, int newLength) 會將 original 複製到一個長度為 newLength 的新陣列中並返回。

newCapacity(int minCapacity) 與 hugeCapacity(int minCapacity)

newCapacity(int minCapacity) 返回了一個大於等於 minCapacity 的整數。擴容時先將原來的容量乘以 2 再加 2,如果容量未達到要求,則將 newCopacity 的值設定為傳入的 minCapacity。
如果計算的 newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0(MAX_ARRAY_SIZE 是一個常量,值為 Integer.MAX_VALUE - 8),則通過 hugeCapacity(int minCapacity) 來決定擴容後大小。

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2; // 將 value.length 乘以 2 再加 2
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

hugeCapacity(int minCapacity) 先判斷是否產生了整數溢位(傳入的 minCapacity 為負數時溢位),溢位了則丟擲異常,否則將容量設定為一個最大不超過 MAX_ARRAY_SIZE 的值。

    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }

總而言之,若 value 的容量較小,則每次容量至少擴容為原來的 2 倍再加 2;若大小還不滿足要求,則直接擴容為 minCapacity,但是最大容量不會超過 MAX_ARRAY_SIZE,也就是說 StringBuffer 表示的字元序列的最大長度為 Integer.MAX_VALUE - 8,繼續 append 則會丟擲 OutofMemoryError() 異常。

trimToSize() 方法

trimtoSize() 方法將 value 的容量壓縮到和字元序列的長度一致,在確定不增加字元序列長度時可以呼叫此方法釋放一部分記憶體。可以釋放 (sbf.capacity() - sbf.length())*2 位元組的記憶體。

    public void trimToSize() {
        if (count < value.length) {
            value = Arrays.copyOf(value, count);
        }
    }

setLength(int newLength) 方法

設定序列的新長度,若 newLength > sbf.length(),則超出部分填充 '\0';若 newLength < sbf.length(),則直接將 count 移動到 newLength 位置,下次直接從 newLength 位置開始追加內容。

    public void setLength(int newLength) {
        if (newLength < 0)
            throw new StringIndexOutOfBoundsException(newLength);
        ensureCapacityInternal(newLength);

        if (count < newLength) {
            Arrays.fill(value, count, newLength, '\0');
        }

        count = newLength;
    }

charAt(int index)

這個方法很簡單,取出下標為 index 的字元,若 index 不在字元索引範圍之內則丟擲 StringIndexOutofBoundsException。

    public char charAt(int index) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        return value[index];
    }

codePointAt(int index)

此方法返回索引為 index 的字元的 16 位 Unicode 編碼系統的碼點。編碼系統將字元對映到一個數字,這個數字就是碼點,例如 'a' 對映的碼點就是 97。需要注意的是,char型別是 2 個位元組,但碼點的的範圍卻可以超過 65535 ,因為有些字元需要用 4 個位元組表示。(參考:Java中碼點和程式碼單元
如:

StringBuilder sb =new StringBuilder("