StringBuilder與StringBuffer作用就是用來處理字符串,但String類本身也具備很多方法可以用來處理字符串,那么為什么還要引入這兩個類呢?
關於String的講解請看Java基礎(三) String深度解析
首先看下面的例子
public static void main(String[] args) {
String str0 = "hel,lo,wor,l,d";
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++){
str0 += i;
}
System.out.println(System.currentTimeMillis() - start);
StringBuilder sb = new StringBuilder("hel,lo,wor,l,d");
long start1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++){
sb.append(i);
}
System.out.println(System.currentTimeMillis() - start1);
StringBuffer sbf = new StringBuffer("hel,lo,wor,l,d");
long start2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++){
sbf.append(i);
}
System.out.println(System.currentTimeMillis() - start2);
}
上述代碼中3處循環完成了同樣的功能,字符串拼接,執行的結果如下:
36823
3
4
可以看出執行時間差別太大,為了解決String不擅長的大量字符串拼接這種業務場景,引入了StringBuffer和StringBuilder.
首先我們分析一下為什么String在大量字符串拼接這種場景下這么慢?
因為String本身不可變,我們對String的任何操作都會返回一個新的對象,然后當前String變量指向新的對象,而原來的String對象就會被GC回收,那么在循環中就會大量快速的創建新的對象,大量原來的對象會不斷的被GC回收,消耗的時間是非常恐怖的,而且內存占用非常大。
下面我們對比了String、StringBuffer與StringBuilder的區別
String | StringBuffer | StringBuilder |
---|---|---|
final修飾,不可繼承 | final修飾,不可繼承 | final修飾,不可繼承 |
字符串常量,創建后不可變 | 字符串變量,可動態修改 | 字符串變量,可動態修改 |
不存在線程安全問題 | 線程安全,所有public方法由synchronized修改 | 線程不安全 |
大量字符串拼接效率最低 | 大量字符串拼接效率非常高 | 大量字符串拼接效率最高 |
StringBuffer與StringBuilder實現非常類似,下面以StringBuilder簡單說明一下append()方法基本原理
1. 首先創建一個StringBuilder
StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder(100);
StringBuilder對字符串的操作是通過char[]來實現的,通過默認構造器創建的StringBuilder,其內部創建的char[]的默認長度為16,當然可以調用重載的構造器傳遞初始長度(推薦這樣,因為這樣可以減少數組擴容次數,提高效率)。
/**
* Constructs a string builder with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuilder() {
super(16);
}
2. StringBuilder的append()方法
每次調用append(str)方法時,會首先判斷數組長度是否足以添加傳遞來的字符串
/**
* Appends the specified string to this character sequence.
* <p>
* The characters of the {@code String} argument are appended, in
* order, increasing the length of this sequence by the length of the
* argument. If {@code str} is {@code null}, then the four
* characters {@code "null"} are appended.
*
* @param str a string.
* @return a reference to this object.
*/
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;
}
/**
* For positive values of {@code minimumCapacity}, this method
* behaves like {@code ensureCapacity}, however it is never
* synchronized.
* If {@code minimumCapacity} is non positive due to numeric
* overflow, this method throws {@code OutOfMemoryError}.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
如果傳遞的字符串長度 + 數組已存放的字符的長度 > 數組的長度,這時就需要進行數據擴容了
/**
* Returns a capacity at least as large as the given minimum capacity.
* Returns the current capacity increased by the same amount + 2 if
* that suffices.
* Will not return a capacity greater than {@code MAX_ARRAY_SIZE}
* unless the given minimum capacity is greater than that.
*
* @param minCapacity the desired minimum capacity
* @throws OutOfMemoryError if minCapacity is less than zero or
* greater than Integer.MAX_VALUE
*/
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
擴容規則如下:默認將數組長度設置為“ (當前數組長度 * 2) + 2”,但如果按此規則擴容后的數組也不足以添加新的字符串,就需要將數組長度設置為“數組內字符長度 + 傳遞的字符串長度”。
因此假如我們知道拼接的字符串大概長度有100多字符,我們就可以設置初始長度150或200,這樣就可以避免或減少數組擴容的次數,從而提高效率。
總結:
本文StringBuffer與StringBuilder的創建,append方法的原理講解,對比了String、StringBuffer與StringBuilder異同,若有不對之處,請批評指正,謝謝!