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;
複製代碼
toString
方法生成的 String 對象,避免每次都要根據編碼生成 String 對象。有若干種構造方法,能夠指定容量大小參數,若是沒有指定則構造方法默認建立容量爲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。分佈式
好比下面該方法加了同步來保證計數的準確性。此外還包含不少其餘方法,好比codePointCount
、capacity
、ensureCapacity
、codePointAt
、codePointBefore
、charAt
、getChars
、setCharAt
、substring
、subSequence
、indexOf
、lastIndexOf
、getBytes
。
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}
複製代碼
該方法用於將該 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
方法,都只是傳入的參數不一樣而已,一樣是使用了 synchronized,另外它還會清理緩存 toStringCache,這是由於 append 後的字符串的值已經變了,因此須要重置緩存。重置緩存的方法還包括:appendCodePoint
、delete
、deleteCharAt
、replace
、insert
、reverse
。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
複製代碼
使用同步操做,先判斷緩存是否爲空,若是爲空則先根據編碼(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);
}
複製代碼
該方法是序列化方法,分別將 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();
}
複製代碼
該方法是反序列方法,分別讀取 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);
}
複製代碼
-------------推薦閱讀------------
跟我交流,向我提問:
公衆號的菜單已分爲「讀書總結」、「分佈式」、「機器學習」、「深度學習」、「NLP」、「Java深度」、「Java併發核心」、「JDK源碼」、「Tomcat內核」等,可能有一款適合你的胃口。
歡迎關注: