开山之作,若讲解不清,思虑不明万望见谅。结论:
(1)String类对象是不可改变的,任何对String对象的改变都会隐形的重新构建一个新的对象,释放原来的String对象
(2)StringBuffer类是可以改变的,任何对StringBuffer对象的改变都不会产生新的对象,StringBuffer对象通过append()方法来修改值
(3)StringBuilder类和StringBuffer类几乎没有任何的差别,只是StringBuilder不支持线程同步不具有线程安全。这就使得在单线程中StringBuilder的效率要高于StringBuffer。
1.为什么在for循环中要慎用String拼接?
从第8行到第35行是循环体,第35行的goto是跳转的意思,在循环体里,每次循环都会创建一个StringBuilder从而获得本次拼接的字符串,我们都知道创建对象是需要开销的。在这里循环的越多,开销也就越大。
(1)String类对象是不可改变的,任何对String对象的改变都会隐形的重新构建一个新的对象,释放原来的String对象
(2)StringBuffer类是可以改变的,任何对StringBuffer对象的改变都不会产生新的对象,StringBuffer对象通过append()方法来修改值
(3)StringBuilder类和StringBuffer类几乎没有任何的差别,只是StringBuilder不支持线程同步不具有线程安全。这就使得在单线程中StringBuilder的效率要高于StringBuffer。
1.为什么在for循环中要慎用String拼接?
效率问题,要清楚的理解这个问题还要从字符串拼接的原理说起。有如下例子:
public class Test
{
public static void main(String args[])
{
String a="a";
String b="b";
String c="c";
String d=a+b+c;
}
}
再上面的例子中实现了两个字符串的拼接,编译之后通过javap -verbose得到如下信息(截取了主要片段):
<span style="font-size:12px;"> Code:
stack=2, locals=5, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String c
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_3
25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore 4
33: return
</span>
从第6行到第21行,我们看到String d=a+b+c;被编译为:
<span style="font-size:12px;">String c=new StringBuilder().append(a).append(b).append(c).toString()</span>
通过新构造一个StringBuilder对象,调用append方法若干次,再toString得到一个新字符串实现的字符串的拼接。
了解了字符串的拼接之后我们再去看看for循环中是怎么样的呢?有如下代码:
<span style="font-size:12px;">public class Test
{
public static void main(String args[])
{
String a="a";
String b="b";
for(int i=0;i<4;i++)
{
a=a+b;
}
}
}</span>
这里实现了一个循环拼接字符串的例子,javap -vrebose得到如下信息(部分截取):
<span style="font-size:12px;"> Code:
stack=2, locals=4, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: iconst_0
7: istore_3
8: iload_3
9: iconst_4
10: if_icmpge 38
13: new #4 // class java/lang/StringBuilder
16: dup
17: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
20: aload_1
21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_2
25: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_1
32: iinc 3, 1
35: goto 8
38: return
</span>
从第8行到第35行是循环体,第35行的goto是跳转的意思,在循环体里,每次循环都会创建一个StringBuilder从而获得本次拼接的字符串,我们都知道创建对象是需要开销的。在这里循环的越多,开销也就越大。
2.String,StringBuilder和StringBuffer性能比较
我们通过for循环来比较三者的性能,有如下代码:
<span style="font-size:12px;">public class Test
{
public static void main(String args[])
{
String a="a";
StringBuilder b=new StringBuilder("b");
StringBuffer c=new StringBuffer("c");
long start_time=0L;
long end_time=0L;
//字符串拼接用时
start_time=System.currentTimeMillis();
for(int i=0;i<100000;i++)
{
a=a+"a";
}
end_time=System.currentTimeMillis();
System.out.println("String use time="+(end_time-start_time));
//StringBuilder用时
start_time=System.currentTimeMillis();
for(int i=0;i<100000;i++)
{
b.append("b");
}
end_time=System.currentTimeMillis();
System.out.println("StringBuilder use time="+(end_time-start_time));
//StringBuffer用时
start_time=System.currentTimeMillis();
for(int i=0;i<100000;i++)
{
c.append("c");
}
end_time=System.currentTimeMillis();
System.out.println("StringBuffer use time="+(end_time-start_time));
}
}
</span>
在我的电脑上执行后的结果为:
<span style="font-size:12px;">String use time=11714
StringBuilder use time=4
StringBuffer use time=5</span>
可见对于同等数量的循环中string的拼接是非常低效的,由于不支持多线程所以StringBuilder的效率比StringBuffer要高。
总结:
在单线程中,若非循环拼接字符串,使用String,StringBuilder和StringBuffer都是可以的,这种情况下String拼接更为简单易懂。StringBuffer略显大材小用。
在单线程循环拼接字符串时,使用StringBuilder最为合理。
在多线程中StringBuffer是不二人选!