StringBuilder
是从
Java 5
以后增加的一个字符串处理类。查看
API
文档,我们可以知道
StringBuilder
和
StringBuffer
提供同样的功能,只是
StringBuilder
不保证线程安全,所以性能比
StirngBuffer
好,并推荐在确定线程安全的情况下,尽量用
StringBuilder
。事实真是如此吗?让我们通过一个小试验来看看
试验设计:
分别用
StringBuilder
和
StringBuffer
将一指定的字符串自连接一百万次,比较两种方法所用的时间。为尽量避免环境的干扰,测试时会关闭本机中其它应用程序,并且为了避免测试组之间的相互干扰,在每组测试完成后会重起机器。每个程序运行十次,最后取平均值。
测试环境:
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))
运行程序时没有为
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
.今天是情人节,祝大家节日快乐,有情人终成眷属!