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("