從JDK源碼看StringBuffer

概況

Java 中處理字符串時常常使用的 String 是一個常量,一旦建立後不能被改變。爲了提供可修改的操做,引入了 StringBuilder 類,可看前面的文章《從JDK源碼看StringBuilder》。但它不是線程安全的,只用在單線程場景下。因此引入了線程安全的 StringBuffer 類,用於多線程場景。java

總的來講主要是經過在必要的方法上加 synchronized 來實現線程安全。數組

三種字符串類關係

這裏寫圖片描述

繼承結構

--java.lang.Object
  --java.lang.AbstractStringBuilder
    --java.lang.StringBuffer
複製代碼

類定義

public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence
複製代碼

StringBuffer 類被聲明爲 final,說明它不能再被繼承。同時它繼承了 AbstractStringBuilder 類,並實現了 Serializable 和 CharSequence 兩個接口。緩存

其中 Serializable 接口代表其能夠序列化。安全

CharSequence 接口用來實現獲取字符序列的相關信息,接口定義以下:bash

  • length()獲取字符序列長度。
  • charAt(int index)獲取某個索引對應字符。
  • subSequence(int start, int end)獲取指定範圍子字符串。
  • toString()轉成字符串對象。
  • chars()用於獲取字符序列的字符的 int 類型值的流,該接口提供了默認的實現。
  • codePoints()用於獲取字符序列的代碼點的 int 類型的值的流,提供了默認的實現。
public interface CharSequence {

    int length();

    char charAt(int index);

    CharSequence subSequence(int start, int end);

    public String toString();

    public default IntStream chars() {
        省略代碼。。
    }

    public default IntStream codePoints() {
        省略代碼。。
    }
}
複製代碼

主要屬性

private transient String toStringCache;
byte[] value;
byte coder;
int count;
複製代碼
  • toStringCache 用於緩存調用toString方法生成的 String 對象,避免每次都要根據編碼生成 String 對象。
  • value 該數組用於存儲字符串值。
  • coder 表示該字符串對象所用的編碼器。
  • count 表示該字符串對象中已使用的字符數。

構造方法

有若干種構造方法,能夠指定容量大小參數,若是沒有指定則構造方法默認建立容量爲16的字符串對象。若是 COMPACT_STRINGS 爲 true,即便用緊湊佈局則使用 LATIN1 編碼(ISO-8859-1編碼),則開闢長度爲16的 byte 數組。而若是是 UTF16 編碼則開闢長度爲32的 byte 數組。多線程

public StringBuffer() {
        super(16);
    }
    
AbstractStringBuilder(int capacity) {
        if (COMPACT_STRINGS) {
            value = new byte[capacity];
            coder = LATIN1;
        } else {
            value = StringUTF16.newBytesFor(capacity);
            coder = UTF16;
        }
    }
    
public StringBuffer(int capacity) {
        super(capacity);
    }
複製代碼

若是構造函數傳入的參數爲 String 類型,則會開闢長度爲str.length() + 16的 byte 數組,並經過append方法將字符串對象添加到 byte 數組中。併發

public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }
複製代碼

相似地,傳入參數爲 CharSequence 類型時也作相同處理。app

public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }
複製代碼

主要方法

爲了實現線程安全,其實最簡單也多是最沒效率的方法就是經過對某些方法進行同步,以此容許併發操做。因此 StringBuffer 和 StringBuilder 其實實現邏輯幾乎都同樣,而且抽象到 AbstractStringBuilder 抽象類中來實現,只是 StringBuffer 將一些必要的方法進行同步處理了。機器學習

StringBuffer 中大多數方法都只是加了 synchronized。分佈式

好比下面該方法加了同步來保證計數的準確性。此外還包含不少其餘方法,好比codePointCountcapacityensureCapacitycodePointAtcodePointBeforecharAtgetCharssetCharAtsubstringsubSequenceindexOflastIndexOfgetBytes

@Override
public synchronized int length() {
        return count;
    }
    
@Override
public synchronized void setLength(int newLength) {
        toStringCache = null;
        super.setLength(newLength);
    }

複製代碼

trimToSize方法

該方法用於將該 StringBuffer 對象的容量壓縮到與字符串長度大小相等。重寫了該方法,主要是添加了同步,保證了數組複製過程的準確性。

@Override
public synchronized void trimToSize() {
        super.trimToSize();
    }

public void trimToSize() {
        int length = count << coder;
        if (length < value.length) {
            value = Arrays.copyOf(value, length);
        }
    }
複製代碼

append方法

有多個append方法,都只是傳入的參數不一樣而已,一樣是使用了 synchronized,另外它還會清理緩存 toStringCache,這是由於 append 後的字符串的值已經變了,因此須要重置緩存。重置緩存的方法還包括:appendCodePointdeletedeleteCharAtreplaceinsertreverse

@Override
public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
複製代碼

toString方法

使用同步操做,先判斷緩存是否爲空,若是爲空則先根據編碼(Latin1 或 UTF16)建立對應編碼佔位的 String 對象,而後建立新 String 對象並返回。

@Override
public synchronized String toString() {
        if (toStringCache == null) {
            return toStringCache =
                    isLatin1() ? StringLatin1.newString(value, 0, count)
                               : StringUTF16.newString(value, 0, count);
        }
        return new String(toStringCache);
    }
複製代碼

writeObject方法

該方法是序列化方法,分別將 value、count、shared 字段的值寫入。

private synchronized void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        java.io.ObjectOutputStream.PutField fields = s.putFields();
        char[] val = new char[capacity()];
        if (isLatin1()) {
            StringLatin1.getChars(value, 0, count, val, 0);
        } else {
            StringUTF16.getChars(value, 0, count, val, 0);
        }
        fields.put("value", val);
        fields.put("count", count);
        fields.put("shared", false);
        s.writeFields();
    }
複製代碼

readObject方法

該方法是反序列方法,分別讀取 value 和 count,而且初始化對象內的字節數組和編碼標識。

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        java.io.ObjectInputStream.GetField fields = s.readFields();
        char[] val = (char[])fields.get("value", null);
        initBytes(val, 0, val.length);
        count = fields.get("count", 0);
    }
    
void initBytes(char[] value, int off, int len) {
        if (String.COMPACT_STRINGS) {
            this.value = StringUTF16.compress(value, off, len);
            if (this.value != null) {
                this.coder = LATIN1;
                return;
            }
        }
        this.coder = UTF16;
        this.value = StringUTF16.toBytes(value, off, len);
    }
複製代碼

-------------推薦閱讀------------

個人2017文章彙總——機器學習篇

個人2017文章彙總——Java及中間件

個人2017文章彙總——深度學習篇

個人2017文章彙總——JDK源碼篇

個人2017文章彙總——天然語言處理篇

個人2017文章彙總——Java併發篇


跟我交流,向我提問:

這裏寫圖片描述

公衆號的菜單已分爲「讀書總結」、「分佈式」、「機器學習」、「深度學習」、「NLP」、「Java深度」、「Java併發核心」、「JDK源碼」、「Tomcat內核」等,可能有一款適合你的胃口。

爲何寫《Tomcat內核設計剖析》

歡迎關注:

這裏寫圖片描述
相關文章
相關標籤/搜索