StringBuilder 是從 Java 5 以后增加的一個字符串處理類。查看 API 文檔,我們可以知道 StringBuilder 和 StringBuffer 提供同樣的功能,只是 StringBuilder 不保證線程安全,所以性能比 StirngBuffer 好,并推薦在確定線程安全的情況下,盡量用 StringBuilder 。事實真是如此嗎?讓我們通過一個小試驗來看看
試驗設計:
分別用 StringBuilder 和 StringBuffer 將一指定的字符串自連接一百萬次,比較兩種方法所用的時間。為盡量避免環境的干擾,測試時會關閉本機中其它應用程序,并且為了避免測試組之間的相互干擾,在每組測試完成后會重起機器。每個程序運行十次,最后取平均值。
測試環境:
CPU: Celeron – M420
RAM:
OS: Window XP Home Edition
JDK: Sun JDK
運行程序時沒有為 JVM 指定任何參數,全部使用默認值
程序段:
1.? 用 StringBuffer
???
private
static
final
int
COUNT
= 1000000;
???
private
static
final
String
TEMPLATE
=
"0123456789"
;
???
public
static
void
useStringBuffer() {
?????? StringBuffer bf =
new
StringBuffer(
""
);
?????? String target =
null
;
??????
long
start = System.currentTimeMillis();
??????
for
(
int
i = 0; i <
COUNT
; i++) {
?????????? bf.append(
TEMPLATE
);
?????? }
?????? target = bf.toString();
??????
long
end = System.currentTimeMillis();
?????? System.
out
.println(
"Use StringBuffer, time is "
+ (end - start));
??? }??
2.?
用
StringBuilder
???
private
static
final
int
COUNT
= 1000000;
???
private
static
final
String
TEMPLATE
=
"0123456789"
;
???
public
static
void
useStringBuilder() {
?????? StringBuilder bf =
new
StringBuilder(
""
);
?????? String target =
null
;
??????
long
start = System.currentTimeMillis();
??????
for
(
int
i = 0; i <
COUNT
; i++) {
?????????? bf.append(
TEMPLATE
);
?????? }
?????? target = bf.toString();
??????
long
end = System.currentTimeMillis();
?????? System.
out
.println(
"Use StringBuilder, time is "
+ (end - start));
??? }
測試結果:
|
StringBuffer |
StringBuilder |
1 |
328 |
328 |
2 |
344 |
312 |
3 |
328 |
328 |
4 |
344 |
312 |
5 |
344 |
328 |
6 |
344 |
312 |
7 |
328 |
328 |
8 |
344 |
312 |
9 |
343 |
328 |
10 |
344 |
328 |
平均值 |
339.1 |
321.6 |
從結果中可以看出兩者的性能差異約為 5.44 %
下面我們將對測試程序做一點點小小的改動,在 new 一個新的 StringBuffer/StringBuilder 時,我們指定一個容量參數。修改的代碼如下:
1.? 用 StringBuffer
???
private
static
final
String
TEMPLATE
=
"0123456789"
;
???
private
static
final
int
COUNT
= 1000000;
???
public
static
void
useStringBuffer() {
??????
StringBuffer bf = new StringBuffer(COUNT * TEMPLATE.length());
?????? String target =
null
;
??????
long
start = System.currentTimeMillis();
??????
for
(
int
i = 0; i <
COUNT
; i++) {
?????????? bf.append(
TEMPLATE
);
?????? }
?????? target = bf.toString();
??????
long
end = System.currentTimeMillis();
?????? System.
out
.println(
"Use StringBuffer, time is "
+ (end - start));
??? }??
2. 用 StringBuilder
???
private
static
final
String
TEMPLATE
=
"0123456789"
;
???
private
static
final
int
COUNT
= 1000000;
???
public
static
void
useStringBuilder() {
??????
StringBuilder bf = new StringBuilder(COUNT * TEMPLATE.length());
?????? String target =
null
;
??????
long
start = System.currentTimeMillis();
??????
for
(
int
i = 0; i <
COUNT
; i++) {
?????????? bf.append(
TEMPLATE
);
?????? }
?????? target = bf.toString();
??????
long
end = System.currentTimeMillis();
?????? System.
out
.println(
"Use StringBuilder, time is "
+ (end - start));
??? }
測試結果:(表格中第一,二組為上一輪測試的結果)
|
StringBuffer |
StringBuilder |
StringBuffer(int) |
StringBuilder(int) |
1 |
328 |
328 |
140 |
94 |
2 |
344 |
312 |
125 |
125 |
3 |
328 |
328 |
125 |
93 |
4 |
344 |
312 |
125 |
125 |
5 |
344 |
328 |
109 |
94 |
6 |
344 |
312 |
125 |
110 |
7 |
328 |
328 |
125 |
110 |
8 |
344 |
312 |
110 |
110 |
9 |
343 |
328 |
140 |
109 |
10 |
344 |
328 |
109 |
125 |
平均值 |
339.1 |
321.6 |
123.3 |
109.5 |
從表中可以看到 StringBuffer(int) 和 StringBuilder(int) 兩者之間的差異為 12.6% 。但我們更應該看到采用不同的構造方法所帶來的性能提升, StringBuffer 提升了 175.02 %, StringBuilder 提升了 193.70% 。原因在于不指定 StirngBuffer/StringBuilder 的容量時,它們內部的字符緩沖區為 16 個字符(無參構造)或字符串參數的長度,當程序不斷的進行 append/insert 操作時,每當字符數超過原有的容量后, StringBuffer/StringBuilder 將不斷的進行自動擴展的工作,這將消耗比較多的時間。
也許有人會說這樣的測試并不能反映真實的情況,因為在實際的開發中很少會在一個方法中構造 / 拼接一個長度為 10*1000000 的字符串的。更通常的情況是在一個方法中構造一個不太長的串,但該方法將被大量的,反復的調用。 OK, 我們可以修改一下測試程序來放映這種情況。
新程序中 contactWith…. 方法用來拼接一個不太長的字符串,該方法被 use…. 方法反復調用十萬次,并記錄總的調用時間。程序如下:
1.? 使用 StringBuffer
???
private
static
final
String
TEMPLATE
=
"0123456789"
;
???
private
static
final
int
COUNT
= 100000;
???
private
static
final
int
COUNT2
= 10;
???
public
static
String contactWithStringBuffer() {
//???? StringBuffer bf = new StringBuffer("");
?????? StringBuffer bf =
new
StringBuffer(
COUNT2
*
TEMPLATE
.length());
??????
for
(
int
i = 0; i <
COUNT2
; i++) {
?????????? bf.append(
TEMPLATE
);
?????? }
??????
return
bf.toString();
??? }
???
???
public
static
void
useStringBuffer() {
??????
long
start = System.currentTimeMillis();
??????
for
(
int
i = 0; i <
COUNT
; i++) {
??????????
contactWithStringBuffer();
?????? }
??????
long
end = System.currentTimeMillis();
?????? System.
out
.println(
"Use StringBuffer, Time is "
+ (end - start));
??? }
2.? 使用 StringBuilder
???
private
static
final
String
TEMPLATE
=
"0123456789"
;
???
private
static
final
int
COUNT
= 100000;
???
private
static
final
int
COUNT2
= 10;
???
public
static
String contactWithStringBuilder() {
//???? StringBuilder bf = new StringBuilder("");
?????? StringBuilder bf =
new
StringBuilder(
COUNT2
*
TEMPLATE
.length());
??????
for
(
int
i = 0; i <
COUNT2
; i++) {
?????????? bf.append(
TEMPLATE
);
?????? }
??????
return
bf.toString();
??? }
???
???
public
static
void
useStringBuilder() {
??????
long
start = System.currentTimeMillis();
??????
for
(
int
i = 0; i <
COUNT
; i++) {
??????????
contactWithStringBuilder();
?????? }
??????
long
end = System.currentTimeMillis();
?????? System.
out
.println(
"Use StringBuilder, Time is "
+ (end - start));
??? }??
測試結果:
|
StringBuffer |
StringBuilder |
StringBuffer(int) |
StringBuilder(int) |
1 |
188 |
156 |
140 |
109 |
2 |
187 |
172 |
141 |
125 |
3 |
188 |
172 |
125 |
110 |
4 |
188 |
172 |
141 |
110 |
5 |
187 |
172 |
125 |
110 |
6 |
188 |
172 |
125 |
109 |
7 |
172 |
172 |
125 |
125 |
8 |
188 |
157 |
125 |
110 |
9 |
203 |
172 |
125 |
110 |
10 |
188 |
172 |
125 |
109 |
平均值 |
187.7 |
168.9 |
129.7 |
112.7 |
在這種情況下, StringBuffer 與 StringBuilder 的性能差別為: 11.13% 和 15.08% (使用 int 構造函數);而用不同的構造函數的性能差差異分別達到: 44.71% ( StringBuffer )和 49.87% ( StringBuilder )。并且為 StringBuffer 指定容量(使用 StirngBuffer(int) )比不指定容量的 StringBuilder 的性能高出 30.22% 。
結論:
1.? 為了獲得更好的性能,在構造 StirngBuffer 或 StirngBuilder 時應盡可能指定它的容量。當然,如果你操作的字符串長度不超過 16 個字符就不用了。
2.? 相同情況下使用 StirngBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的性能提升,但卻要冒多線程不安全的風險。而在現實的模塊化編程中,負責某一模塊的程序員不一定能清晰地判斷該模塊是否會放入多線程的環境中運行,因此:除非你能確定你的系統的瓶頸是在 StringBuffer 上,并且確定你的模塊不會運行在多線程模式下,否則還是用 StringBuffer 吧 J
3.? 用好現有的類比引入新的類更重要。很多程序員在使用 StringBuffer 時是不指定其容量的(至少我見到的情況是這樣),如果這樣的習慣帶入 StringBuilder 的使用中,你將只能獲得 10 %左右的性能提升(不要忘了,你可要冒多線程的風險噢);但如果你使用指定容量的 StringBuffer ,你將馬上獲得 45% 左右的性能提升,甚至比不使用指定容量的 StirngBuilder 都快 30% 左右。
特別聲明:
1 .本人是基于 Window XP 環境,用 Sun 的 JDK 1.6 完成的以上測試。測試的結果是否能反映其它操作系統(如 Linux, Unix 等)和不同的 JDK (IBM, Weblogic 等 ) 的情況就不得而知,有興趣的網友可以在不同的環境中測試,歡迎您告訴我測試結果。
2 .本人也歡迎對本測試的試驗設計和樣例代碼的合理性和完備性進行討論,但請就事論事。不要扔磚頭(西紅柿是可以的,不過不要壞的;雞蛋也可以,但不要臭的,呵呵)
3 .今天是情人節,祝大家節日快樂,有情人終成眷屬!