StringBuilder
是從
Java 5
以后增加的一個(gè)字符串處理類。查看
API
文檔,我們可以知道
StringBuilder
和
StringBuffer
提供同樣的功能,只是
StringBuilder
不保證線程安全,所以性能比
StirngBuffer
好,并推薦在確定線程安全的情況下,盡量用
StringBuilder
。事實(shí)真是如此嗎?讓我們通過(guò)一個(gè)小試驗(yàn)來(lái)看看
?
試驗(yàn)設(shè)計(jì):
分別用
StringBuilder
和
StringBuffer
將一指定的字符串自連接一百萬(wàn)次,比較兩種方法所用的時(shí)間。為盡量避免環(huán)境的干擾,測(cè)試時(shí)會(huì)關(guān)閉本機(jī)中其它應(yīng)用程序,并且為了避免測(cè)試組之間的相互干擾,在每組測(cè)試完成后會(huì)重起機(jī)器。每個(gè)程序運(yùn)行十次,最后取平均值。
?
測(cè)試環(huán)境:
CPU: Celeron – M420
RAM: 1G
OS: Window XP Home Edition
JDK: Sun JDK 1.6.0 (Java HotSpot? Client VM (build 1.6.0-b105, mixed mode, sharing))
運(yùn)行程序時(shí)沒(méi)有為
JVM
指定任何參數(shù),全部使用默認(rèn)值
?
程序段:
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));
??? }
?
?
測(cè)試結(jié)果:
?
|
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
|
?
從結(jié)果中可以看出兩者的性能差異約為
5.44
%
?
下面我們將對(duì)測(cè)試程序做一點(diǎn)點(diǎn)小小的改動(dòng),在
new
一個(gè)新的
StringBuffer/StringBuilder
時(shí),我們指定一個(gè)容量參數(shù)。修改的代碼如下:
?
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));
??? }
?
測(cè)試結(jié)果:(表格中第一,二組為上一輪測(cè)試的結(jié)果)
?
|
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%
。但我們更應(yīng)該看到采用不同的構(gòu)造方法所帶來(lái)的性能提升,
StringBuffer
提升了
175.02
%,
StringBuilder
提升了
193.70%
。原因在于不指定
StirngBuffer/StringBuilder
的容量時(shí),它們內(nèi)部的字符緩沖區(qū)為
16
個(gè)字符(無(wú)參構(gòu)造)或字符串參數(shù)的長(zhǎng)度,當(dāng)程序不斷的進(jìn)行
append/insert
操作時(shí),每當(dāng)字符數(shù)超過(guò)原有的容量后,
StringBuffer/StringBuilder
將不斷的進(jìn)行自動(dòng)擴(kuò)展的工作,這將消耗比較多的時(shí)間。
?
也許有人會(huì)說(shuō)這樣的測(cè)試并不能反映真實(shí)的情況,因?yàn)樵趯?shí)際的開(kāi)發(fā)中很少會(huì)在一個(gè)方法中構(gòu)造
/
拼接一個(gè)長(zhǎng)度為
10*1000000
的字符串的。更通常的情況是在一個(gè)方法中構(gòu)造一個(gè)不太長(zhǎng)的串,但該方法將被大量的,反復(fù)的調(diào)用。
OK,
我們可以修改一下測(cè)試程序來(lái)放映這種情況。
?
新程序中
contactWith….
方法用來(lái)拼接一個(gè)不太長(zhǎng)的字符串,該方法被
use….
方法反復(fù)調(diào)用十萬(wàn)次,并記錄總的調(diào)用時(shí)間。程序如下:
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));
??? }??
?
測(cè)試結(jié)果:
?
|
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
構(gòu)造函數(shù));而用不同的構(gòu)造函數(shù)的性能差差異分別達(dá)到:
44.71%
(
StringBuffer
)和
49.87%
(
StringBuilder
)。并且為
StringBuffer
指定容量(使用
StirngBuffer(int)
)比不指定容量的
StringBuilder
的性能高出
30.22%
。
?
結(jié)論:
1.?
為了獲得更好的性能,在構(gòu)造
StirngBuffer
或
StirngBuilder
時(shí)應(yīng)盡可能指定它的容量。當(dāng)然,如果你操作的字符串長(zhǎng)度不超過(guò)
16
個(gè)字符就不用了。
2.?
相同情況下使用
StirngBuilder
相比使用
StringBuffer
僅能獲得
10%~15%
左右的性能提升,但卻要冒多線程不安全的風(fēng)險(xiǎn)。而在現(xiàn)實(shí)的模塊化編程中,負(fù)責(zé)某一模塊的程序員不一定能清晰地判斷該模塊是否會(huì)放入多線程的環(huán)境中運(yùn)行,因此:除非你能確定你的系統(tǒng)的瓶頸是在
StringBuffer
上,并且確定你的模塊不會(huì)運(yùn)行在多線程模式下,否則還是用
StringBuffer
吧
J
3.?
用好現(xiàn)有的類比引入新的類更重要。很多程序員在使用
StringBuffer
時(shí)是不指定其容量的(至少我見(jiàn)到的情況是這樣),如果這樣的習(xí)慣帶入
StringBuilder
的使用中,你將只能獲得
10
%左右的性能提升(不要忘了,你可要冒多線程的風(fēng)險(xiǎn)噢);但如果你使用指定容量的
StringBuffer
,你將馬上獲得
45%
左右的性能提升,甚至比不使用指定容量的
StirngBuilder
都快
30%
左右。
?
特別聲明:
1
.本人是基于
Window XP
環(huán)境,用
Sun
的
JDK 1.6
完成的以上測(cè)試。測(cè)試的結(jié)果是否能反映其它操作系統(tǒng)(如
Linux, Unix
等)和不同的
JDK (IBM, Weblogic
等
)
的情況就不得而知,有興趣的網(wǎng)友可以在不同的環(huán)境中測(cè)試,歡迎您告訴我測(cè)試結(jié)果。
2
.本人也歡迎對(duì)本測(cè)試的試驗(yàn)設(shè)計(jì)和樣例代碼的合理性和完備性進(jìn)行討論,但請(qǐng)就事論事。不要扔磚頭(西紅柿是可以的,不過(guò)不要壞的;雞蛋也可以,但不要臭的,呵呵)
3
.今天是情人節(jié),祝大家節(jié)日快樂(lè),有情人終成眷屬!