如何讓你的程序運行的更快(1)之續---揭秘StringBuffer的capacity
馬嘉楠
2006-09-19
前幾天寫了一篇文章“ 如何讓你的程序運行的更快(1)---String VS StringBuffer ”,文章在情景三中提到了如何通過“設置StringBuffer的容量來提升性能”,其中有個問題我沒有想明白,就是為什么StringBuffer的容量自動增加的時候是“2*舊值+2”呢?
雖然問題依然沒有解決,不過也發現了不少有趣的問題,在此和大家分享 。希望能讓你有所收獲,歡迎大家一起討論。
注:需要用到的函數說明:
capacity():Returns the current capacity of the String buffer.?
?????????????????????The capacity is the amount of storage available for newly inserted characters;??
??????????????????????beyond which an allocation will occur.
length():???Returns the length (character count) of this string buffer.
?
一.StringBuffer的默認capacity
例1:
System.out.println( " with?no?characters,?the?initial?capacity?of?StringBuffer?is? " ? + ?sb.capacity());
System.out.println( " and?the?length?of?the?StringBuffer?is? " ? + ?sb.length());
輸出:
and?the?length?of?the?StringBuffer?is? 0
結論: StringBuffer的默認容量(capacity)為16
原因:
StringBuffer的默認構造函數為
?????? this ( 16 ?);
}?
此時默認構造函數又調用了StringBuffer的代參數的構造函數,設置字符串數組value長度為16,如下:
private ? boolean ?shared;???????? // A?flag?indicating?whether?the?buffer?is?shared
public ?StringBuffer( int ?length)?{
??????value? = ? new ? char [length];
??????shared? = ? false ;
}
?
// 調用capacity()返回字符串數組value的長度,即StringBuffer的容量(capacity)
public ? synchronized ? int ?capacity()?{
??????return ?value.length;
}
二.用字符串初始化StringBuffer的內容
在聲明一個StringBuffer變量的時候,用字符串進行初始化,容量會有變化么?
例2:
StringBuffer?sb1? = ? new ?StringBuffer( " hello?world " ?);
System.out.println( " with?characters,?the?capacity?of?StringBuffer?is? " ? + ??sb1.capacity());
System.out.println( " but?the?length?of?the?StringBuffer?is? " ? + ?sb1.length());?

// 利用append()來設置StringBuffer的內容
StringBuffer?sb11? = ? new ?StringBuffer();
sb11.append( " hello?world " );
System.out.println( " with?append(),?the?capacity?of?StringBuffer?is? " ? + ?sb11.capacity());
System.out.println( " but?the?length?of?the?StringBuffer?is? " ? + ?sb11.length());
兩者輸出結果會一樣么?
你一定認為,這不是顯然的么。用長度為11的字符串“hello world”進行初始化,其長度11小于StringBuffer的默認容量16。所以兩者結果都為capacity=16,length=11。
那么實際結果如何呢?
輸出:
but the length of the StringBuffer is 11
with append(), the capacity of StringBuffer is 16
but the length of the StringBuffer is 11
疑問:
怎么第一種方法的StringBuffer的capacity是27(16+11)呢?
原因:
StringBuffer的帶參數的構造函數
2???????this(str.length()?+?16 );
3??????? append(str);
4?}
結論:
StringBuffer的capacity等于用來初始化的字符串長度(11)加上StringBuffer的默認容量(16),而不是我們想當然的在默認容量16中拿出11個來存放字符串“hello world”。
如果我們不設置StringBuffer的capacity,分別對兩者繼續追加字符串,任其自動增長,其容量增長如下:
第一種情況:27,56,114,230,462,926...,
第二種情況:16,34,70? ,142,286,574...,
(為什么容量增加會是這種規律,后面會做解釋)。
我想情況2節省空間的概率大一些,因為StringBuffer的capacity的增長比情況1慢,每次增加的空間小一些。
所以以后寫代碼的時候可以考慮使用第二種方法(使用StringBuffer的append()),特別是初始化字符串很長的情況。當然這會多寫一行代碼^+^:
三.StringBuffer的capacity變化
例3:
for ( int ?i = 0 ;?i < 12 ;?i ++ ){
??????sb3.append(i);
}
System.out.println( " before?changed,?the?capacity?is? " ? + ?sb3.capacity());
System.out.println( " and?the?length?is? " ? + ?sb3.length());?
for ( int ?i = 0 ;?i < 10 ;?i ++ ){
??????sb3.append(i);
}
System.out.println( " first?time?increased,?the?capacity?is? " ? + ?sb3.capacity());
System.out.println( " and?the?length?is? " ? + ?sb3.length());?
for ( int ?i = 0 ;?i < 11 ;?i ++ ){
??????sb3.append(i);
}
System.out.println( " second?time?increased,?the?capacity?is? " ? + ?sb3.capacity());
System.out.println( " and?the?length?is? " ? + ?sb3.length());?
輸出:
and?the?length?is? 14
first?time?increased,?the?capacity?is? 34
and?the?length?is? 24
second?time?increased,?the?capacity?is? 70
and?the?length?is? 36 ?
奇怪,benfore changed怎么長度不是12而是14呢?哈哈,開始我也困惑了一下,仔細想想就會明白的,你可以輸出sb3看看System.out.println("the content of sb3 is " + sb3.toString());
結論:
capacity增長的規律為 (舊值+1)*2
原因:
StringBuffer的容量增加函數expandCapacity():
int ?newCapacity? = ?(value.length? + ? 1 )? * ? 2 ;
if ?(newCapacity? < ? 0 )?{
??????newCapacity? = ?Integer.MAX_VALUE;
}? else ? if ?(minimumCapacity? > ?newCapacity)?{
??????newCapacity? = ?minimumCapacity;
}
?
char ?newValue[]? = ? new ? char [newCapacity];
System.arraycopy(value,? 0 ,?newValue,? 0 ,?count);
value? = ?newValue;
shared? = ? false ;
}?
疑問:
為什么要(舊值+1)*2呢?
我自己的想法:
也許是考慮到value.length的值可能為0(初始化時設置StringBuffer的capactity為0).
或者考慮到溢出的情況?
但是可能是JVM的某些限制,我的機器數組最大可以設置為30931306,再大就報錯:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
30931306這個數字好奇怪
還有哪方面的考慮么?誰知道,能告訴我么?
四.StringBuffer的capacity只能大不能小
例5:
?2?System.out.println("before?ensureCapacity(),?the?capacity?is?"?+ ?sb4.capacity());
?3?sb4.ensureCapacity(10 );
?4?System.out.println("after?ensureCapacity(10),?the?capacity?is?"?+ ?sb4.capacity());
?5? ????????
?6?System.out.print("now,?the?capacity?is?"?+?sb4.capacity()?+?",?" );
?7?sb4.ensureCapacity(20 );
?8?System.out.println("after?ensureCapacity(20),?the?capacity?is?"?+ ?sb4.capacity());
?9? ????????
10?System.out.print("now,?the?capacity?is?"?+?sb4.capacity()?+?",?" );
11?sb4.ensureCapacity(80 );
12?System.out.println("after?ensureCapacity(80),?the?capacity?is?"?+?sb4.capacity());
輸出:
after?ensureCapacity( 10 ),?the?capacity?is? 16
now,?the?capacity?is? 16 ,?after?ensureCapacity( 20 ),?the?capacity?is? 34
now,?the?capacity?is? 34 ,?after?ensureCapacity( 80 ),?the?capacity?is? 80
結論:
當設置StringBuffer的容量
1、小于當前容量時,容量不變。
??????本例中,容量依然為16。
2、大于當前容量,并且小于(當前容量+1)*2,則容量變為(當前容量+1)*2。
??????本例中,16<20<(16+1)*2=34,所以容量為34。
3、大于當前容量,并且大于(當前容量+1)*2,則容量變為用戶所設置的容量。
??????本例中,80>16,80>(16+1)*2=34,所以容量為80。
原因:
函數:ensureCapacity( )和 expandCapacity( )進行了控制
?????? if ?(minimumCapacity? > ?value.length)?{
// 當設置StringBuffer的容量小于當前容量時,容量不變。
????????????expandCapacity(minimumCapacity);
??????}
}
private ? void ?expandCapacity( int ?minimumCapacity)?{
??????int ?newCapacity? = ?(value.length? + ? 1 )? * ? 2 ;
??????if ?(newCapacity? < ? 0 )?{
????????????newCapacity? = ?Integer.MAX_VALUE;
??????}? else ? if ?(minimumCapacity? > ?newCapacity)?{
??????// 當設置StringBuffer的容量大于(當前容量+1)*2,則容量變為用戶所設置的容量。
??????// 否則,容量為(當前容量+1)*2,即newCapacity
????????????newCapacity? = ?minimumCapacity;
??????}
?
char ?newValue[]? = ? new ? char [newCapacity];
System.arraycopy(value,? 0 ,?newValue,? 0 ,?count);
value? = ?newValue;
shared? = ? false ;
}?
?
注:
問一下
String str = String.valueOf(null);
System.out.println(str.length());
你們執行的話會出錯么?
我的報錯,我的是jdk1.4
因為StringBuffer中的append(String str)函數中有這樣的語句,
if ?(str? == ? null )?{
??????str? = ?String.valueOf(str);
}
int ?len? = ?str.length(); // len有可能得負值么?
int ?newcount? = ?count? + ?len;
if ?(newcount? > ?value.length)
expandCapacity(newcount);
str.getChars( 0 ,?len,?value,?count);
count? = ?newcount;
return ? this ;
}
?
馬嘉楠
jianan.ma@gmail.com