一、介紹
1、StringBuilder和StringBuffer的相同點
StringBuilder和StringBuffer都代表可變字符序列的類,上節講的String是不可變字符序列,StringBulder和StringBuffer都繼承了AbstractStringBuilder,除了序列化相關的其他操作都是調用父類AbstractStringBuilder的方法實現的,兩個類代碼基本上相同。
2、StringBuilder和StringBuffer的不同點
StriingBuilder不是線程安全的,用在單線程環境,而StringBuffer是線程安全的,可以用在多線程環境,StringBuffer的方法都添加了synchronized關鍵字,synchronized就是java中的內置鎖,多個線程對同一個StringBuffer操作時需要獲取鎖,StringBuffer爲了提升性能,toString方法使用了緩存,我們看以下代碼。
private transient char[] toStringCache;
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
從代碼中我們看出,如果toStringCache爲空,則調用Arrays.copyOfRange(value, 0, count)拷貝當前StringBuffer內部數組的數據並賦值給toStringCache,如果StringBuffer對象沒有改變,則下次調用就省去了拷貝數據的開銷,直接使用toStringCache。如果StringBuffer對象有修改操作就會將toStringCache置空,下次調用toString還要重新拷貝數據,讀取操作不會置空toStringCache。置空就是將toStringCache引用賦值nul,我們也看到了toString方法添加了synchronized關鍵字。
二、代碼講解
因爲StringBuilder和StringBuffer的大部分操作都是調用父類AbstractStringBuilder的方法,因此我們除了構造函數以外只講解AbstractStringBuilder的代碼。
StringBuilder構造函數(StringBuffer類似)
public StringBuilder() {
super(16);
}
無參構造函數,調用父類構造函數,創建大小爲16的字符數組。
public StringBuilder(int capacity) {
super(capacity);
}
創建指定大小的字符數組
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
字符串或者字符序列爲參數的構造函數,先調用父類的構造函數創建字符串或者字符序列的長度加上16,爲什麼加上16呢,因爲StringBuilder和StringBuffer是可變字符序列,因此繼續添加字符串的可能性很大,因此原長度加上16可以避免內部char數組的擴容,前提添加的字符串小於16.
AbstractStringBuilder
1、屬性
char[] value;存儲數據的數組
int count; 當前數量大小
2、構造方法
//子類序列化需要
AbstractStringBuilder() {
}
//創建指定大小的char數組
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
3、常用方法
public void ensureCapacity(int minimumCapacity) 確保char數組容量至少等於minimumCapacity,如果當前char數組容量小於minimumCapacity,將進行擴容,擴容是新建一個當前char數組容量乘以2加2的新數組,並把當前char數組的數據拷貝到新建數組中去。如果minimumCapacity爲負數將不進行任何操作。
public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0) //1
ensureCapacityInternal(minimumCapacity);
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) { //2
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2; //3
if (newCapacity - minCapacity < 0) { //4
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) //5
? hugeCapacity(minCapacity)
: newCapacity;
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow //6
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE) //7
? minCapacity : MAX_ARRAY_SIZE;
}
1、指定的minimumCapacity如果小於0則直接返回
2、指定的minimumCapacity大於當前char數組容量才進行擴容,先調用newCapacity計算新容量,然後調用Arrays.copyOf拷貝一個指定大小的新數組並賦值給當前對象的char數組,新數組中包含原來數組的所有值。Arrays.copyOf使用了System.arraycopy方法,上節已經介紹過,不再贅述。
3、value.length左移一位,等於乘以2,然後加2,計算擴容的數組大小,爲什麼乘以2呢?因爲如果擴容後的數組過大會造成存儲空間的浪費,如果擴容後的數組過小會造成數組的頻繁擴容影響性能,因此爲了權衡選擇了兩倍擴容,兩倍擴容也是計算機領域常用方法。爲什麼後面還要加2呢?在value的大小爲0的情況下,如果不加2newCapacity的大小是0,會造成擴容失敗,當然有人會有疑問了,什麼情況下當前的char數組大小爲0呢,子類的構造函數不是創建了大小爲16的數組嗎?關鍵在trimToSize方法,如果當前對象的count爲0,調用此方法當前數組會縮減爲大小爲0的數組。
4、如果根據當前char數組的大小擴容後的newCapacity小於指定的minCapacity,則使用minCapacity當作擴容後的數組大小。
5、如果newCapacity小於0(溢出)或者newCapacity大於Integer.MAX_VALUE - 8,說明指數擴容後的大小或者指定的minCapacity都過大,則調用hugeCapacity計算容量,否則返回指數擴容後的容量。
6、指定的minCapacity大於Integer.MAX_VALUE,直接拋出OOM異常
7、minCapacity大於Integer.MAX_VALUE - 8則返回minCapacity,否則返回Integer.MAX_VALUE - 8
public AbstractStringBuilder append(String str) 添加指定的字符串到當前字符序列
public AbstractStringBuilder append(String str) {
if (str == null) //1
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len); //3
str.getChars(0, len, value, count); //4
count += len;
return this;
}
private AbstractStringBuilder appendNull() { //2
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重載方法實現大都類似,不再講解。
1、如果指定字符串爲null,則調用appendNull將‘null’四個字符串添加到char數組中,而不是什麼都沒有添加。
2、appendNull方法先調用ensureCapacityInternal確保當前數組容量是否能夠存儲count+4大小的數據,然後當前char數組添加null四個字符,更新count後返回。
3、調用ensureCapacityInternal確保容量足夠添加指定字符串
4、調用str.getChars(0, len, value, count)將str的數據拷貝到value從count的開頭處,getChars方法內部也是調用System.arraycopy,更新count後返回。
public AbstractStringBuilder insert(int offset, String str) 指定位置處插入字符串
public AbstractStringBuilder insert(int offset, String str) {
if ((offset < 0) || (offset > length())) //1
throw new StringIndexOutOfBoundsException(offset);
if (str == null) //2
str = "null";
int len = str.length();
ensureCapacityInternal(count + len); //3
System.arraycopy(value, offset, value, offset + len, count - offset); //4
str.getChars(value, offset); //5
count += len;
return this;
}
1、確保offset參數是否合法,非法則拋出索引越界異常。
2、如果指定字符串爲null,則將字符串指定“null”字符串,字符序列會添加"null"字符串。
3、確保容量大小是否足夠。
4、 調用System.arraycopy(value, offset, value, offset + len, count - offset)將value中索引爲offset以後的字符序列拷貝到offset + len處,len爲指定字符串的長度,offset到offset + len處的索引存儲指定的字符串str
5、調用str.getChars(value, offset)將指定字符串str拷貝到value的索引offset處,value中的索引offset到offset + len存儲了指定字符串str.,更新count後返回。
AbstractStringBuilder delete(int start, int end) 刪除索引start到end的字符序列
public AbstractStringBuilder delete(int start, int end) {
if (start < 0) //1
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;
if (start > end)
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) { //2
System.arraycopy(value, start+len, value, start, count-end);
count -= len;
}
return this;
1、判斷start和end是否合法
2、將val中start+len索引後的字符序列拷貝到start索引處,start索引到start+len索引的字符序列被start+len索引後的字符序列覆蓋,相當與刪除,更新count後返回。注意,System.arraycopy只是拷貝數組中的數據,原來位置的數據仍然沒有變,只不過toString的時候只獲取char數組中count範圍內的字符序列。
public AbstractStringBuilder reverse() 字符序列翻轉
public AbstractStringBuilder reverse() {
boolean hasSurrogates = false;
int n = count - 1;
for (int j = (n-1) >> 1; j >= 0; j--) { //1
int k = n - j;
char cj = value[j];
char ck = value[k];
value[j] = ck;
value[k] = cj;
if (Character.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) { //2
reverseAllValidSurrogatePairs();
}
return this;
}
/** Outlined helper method for reverse() */
private void reverseAllValidSurrogatePairs() {
for (int i = 0; i < count - 1; i++) { //3
char c2 = value[i];
if (Character.isLowSurrogate(c2)) {
char c1 = value[i + 1];
if (Character.isHighSurrogate(c1)) {
value[i++] = c1;
value[i] = c2;
}
}
}
}
1、遍歷char數組,計算j和k,j相當於中位數往左第一個字符,k相當於中位數往右一個字符,依次遞減j,遞增k,交換j處和k處的字符,完成char數組中的字符的翻轉,hasSurrogates 記錄字符數組中是否存在增補字符的標識,增補字符是由兩個char組成,第一個char是高代理項,第二個char是低代理項,大家自行查詢資料。
2、hasSurrogates爲true,char數組中存在增補字符,調用reverseAllValidSurrogatePairs處理增補字符
3、遍歷char數組,如果當前索引下存在增補字符,說明這個char是翻轉後的增補字符,我們舉個例子,例如1234字符串翻轉後爲4321,如果12是增補字符,翻轉不應該改變增補字符的順序,則翻轉後應該是4312,因此我們要改變翻轉後增補字符兩個char的順序,翻轉前增補字符是高代理項在前,低代理項在後,翻轉後低代理項在前,高代理項在後,因此代碼中isLowSurrogate判斷索引下的char是否是低代理項,是的話與下一個高代理項char交換順序。
其他方法大都類似或者調用的String的方法實現,不再講解,下篇文章給大家講解Integer類。