概況
Java 中處理字串時經常使用的 String 是一個常量,一旦建立後不能被改變。為了提供可修改的操作,引入了 StringBuilder 類,可看前面的文章《從JDK原始碼看StringBuilder》。但它不是執行緒安全的,只用在單執行緒場景下。所以引入了執行緒安全的 StringBuffer 類,用於多執行緒場景。
總的來說主要是通過在必要的方法上加 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 介面用來實現獲取字元序列的相關資訊,介面定義如下:
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 型別時也做相同處理。
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);
}
複製程式碼
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 後的字串的值已經變了,所以需要重置快取。重置快取的方法還包括:appendCodePoint
、delete
、deleteCharAt
、replace
、insert
、reverse
。
@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);
}
複製程式碼
-------------推薦閱讀------------
跟我交流,向我提問:
公眾號的選單已分為“讀書總結”、“分散式”、“機器學習”、“深度學習”、“NLP”、“Java深度”、“Java併發核心”、“JDK原始碼”、“Tomcat核心”等,可能有一款適合你的胃口。
歡迎關注: