之前我們在另一篇博客講過,String是被final修飾的,是一個不可變對象。(點擊這裏查看)我們如果要修改String的內容,就只能通過重新new一個對象來實現。毫無疑問,這是非常耗內存的,當我們需要不斷的更改String對象的內容時,我們的內存空間很容易溢出。因此,我們有了StringBuffer和StringBuilder這兩個替代品。
StringBuffer
StringBuffer類繼承自AbstractStringBuilder抽象類,實現了Serializable序列化接口和CharSequence接口。
StringBuffer的構造方法
Constructor and Description |
---|
StringBuffer()
構造一個沒有字符的字符串緩衝區,初始容量爲16個字符。 |
StringBuffer(CharSequence seq)
構造一個包含與指定的相同字符的字符串緩衝區 |
StringBuffer(int capacity)
構造一個沒有字符的字符串緩衝區和指定的初始容量。 |
StringBuffer(String str)
構造一個初始化爲指定字符串內容的字符串緩衝區。 |
StringBuffer的常用方法
StringBuffer |
append(String str)
將指定的字符串附加到此字符序列。 |
char |
charAt(int index)
返回 |
StringBuffer |
delete(int start, int end)
刪除此序列的子字符串中的字符。 |
int |
indexOf(String str)
返回指定子字符串第一次出現的字符串內的索引。 |
StringBuffer |
insert(int offset, String str)
將字符串插入到此字符序列中。 |
int |
length()
返回長度(字符數)。 |
String |
substring(int start)
返回一個新的 |
String |
substring(int start, int end)
返回一個新的 |
StringBuffer初始化及擴容機制
StringBuffer與String最大的不同在於StringBuffer是可變的對象,它可以根據要存儲的字符串長度來改變自己的容量,這離不開它本身的擴容機制。首先我們通過StringBuffer的構造函數來了解它不同的初始化方式。
- StringBuffer():StringBuffer的初始容量可以容納16個字符,當該對象的實體存放的字符長度大於16時,實體容量就自動增加。StringBuffer對象可以通過length()方法獲取實體中存放的字符序列長度,通過capacity()方法來獲取當前實體的實際容量。
- StringBuffer(int size):可以指定分配給該對象的實體的初始容量參數爲參數size指定的字符個數。當該對象的實體存放的字符序列的長度大於size個字符時,實體的容量就自動的增加。以便存放所增加的字符。
- StringBuffer(String s):可以指定給對象的實體的初始容量爲參數字符串s的長度額外再加16個字符。當該對象的實體存放的字符序列長度大於size個字符時,實體的容量自動的增加,以便存放所增加的字符。
接下來我們來通過append()方法來了解它的擴容方法。
使用append()方法在字符後面追加東西的時候,如果長度超過了該字符串存儲空間大小了就需要進行擴容:構建新的存儲空間,將舊的複製過去。我們可以從它的源碼很容易看出來這個過程:
//AbstractStringBuilder的方法
public AbstractStringBuilder append(String str) {
//判空
if (str == null) str = "null";
int len = str.length();
//判斷是否足夠存儲str字符串,若不夠則調用expandCapacity方法進行擴容
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//判斷是否需要擴容
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
//擴容方法
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
//將字符串複製過去 String類的方法
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
/* srcBegin - 以此偏移開始複製。
srcEnd - 在此偏移處停止複製。
dst - 將數據複製到的數組。
dstBegin - 偏移到 dst 。*/
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
從源碼我們可以更加清晰的瞭解StringBuffer的擴容過程。當我們進行字符串append時,會先計算添加後字符串大小,傳入一個方法ensureCapacityInternal()進行擴容判斷,需要擴容則調用expandCapacity方法進行擴容。嘗試將新容量擴爲原大小的2倍+2,然後再進行一次if判斷,容量如果不夠,直接擴充到需要的容量大小,即更新後字符串的大小。
StringBuilder和StringBuffer
StringBuilder所繼承的抽象類和實現的接口都與StringBuffer一模一樣,他們兩者的方法和功能完全是等價的。只是StringBuffer中的方法大都採用synchronized關鍵字進行修飾,因此是線程安全的,而StringBuilder沒有,可以被認爲是線程不安全的。由於使用了synchronized關鍵字,所以在單線程程序下,StringBuilder效率更快。
總結
- 從字符串底層存儲對象來看,String是不可變對象,而StringBuilder和StringBuffer是可變長度對象。
- 從線程安全來看,StringBuffer是線程安全對象,因爲很多方法被synchronized方法修飾,而String和StringBuilder是線程不安全的。
- 從效率來看,大部分情況下StringBuilder>StringBuffer>String.
參考:https://blog.csdn.net/longfulong/article/details/78700239