您的位置:首页 > 产品设计 > UI/UE

深入分析Java使用+和StringBuilder进行字符串拼接的差异

2015-11-19 09:55 591 查看
本文转载自深入分析Java使用+和StringBuilder进行字符串拼接的差异 - 临渊羡鱼不如退而结网 - ITeye技术网站,并对原文有一定的删改,我觉得对于很多人来说没有必要的部分进行的删除。

部分原文:

今天看到有网友在我的博客留言,讨论java中String在进行拼接时使用+和StringBuilder和StringBuffer中的执行速度差异很大,而且之前看的书上说java在编译的时候会自动将+替换为StringBuilder或StringB
4000
uffer,但对于这些我都没有做深入的研究,今天准备花一点时间,仔细研究一下。

       首先看一下java编译器在编译的时候自动替换+为StringBuilder或StringBuffer的部分,代码如下。

       测试环境为win764位系统,8G内存,CPU为 i5-3470,JDK版本为32位的JDK1.6.0_38

       第一次使用的测试代码为: public static void main(String[] args) {
// TODO Auto-generated method stub
String demoString="";
int execTimes=10000;
if(args!=null&&args.length>0)
{
execTimes=Integer.parseInt(args[0]);
}
System.out.println("execTimes="+execTimes);
long starMs=System.currentTimeMillis();
for(int i=0;i<execTimes;i++)
{
demoString=demoString+i;
}
long endMs=System.currentTimeMillis();
System.out.println("+ exec millis="+(endMs-starMs));
}
输入不同参数时的执行时间如下:
C:\>java StringAppendDemo 100
execTimes=100
+ exec millis=0
C:\>java StringAppendDemo 1000
execTimes=1000
+ exec millis=6
C:\>java StringAppendDemo 10000
execTimes=10000
+ exec millis=220
C:\>java StringAppendDemo 100000
execTimes=100000
+ exec millis=44267

可以看到,输入的参数为10000和100000时,其执行时间从0.2秒到了44秒。

我们先使用javap命令看一下编译后的代码:

javap –c StringAppendDemo

这里我摘录了和循环拼接字符串有关的那部分代码,具体为:
51: lstore_3
52: iconst_0
53: istore 5
55: iload 5
57: iload_2
58: if_icmpge 87
61: new #5; //class java/lang/StringBuilder
64: dup
65: invokespecial #6; //Method java/lang/StringBuilder."<init>":()V
68: aload_1
69: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
72: iload 5
74: invokevirtual #9; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
77: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
80: astore_1
81: iinc 5, 1
84: goto 55

可以看到,之前的+的确已经被编译为了StringBuilder对象的append方法。通过这里的字节码可以看到,对于每一个+都将被替换为一个StringBuilder而不是我所想象的只生成一个对象。也就是说,如果有10000个+号就会生成10000个StringBuilder对象。具体参看上面字节码的第84行,此处是执行完一次循环以后,再次跳转到55行去执行。

接着,我们把再写一个使用StringBuilder直接实现的方式,看看有什么不一样。

具体代码为:
public class StringBuilderAppendDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
String demoString="";
int execTimes=10000;
if(args!=null&&args.length>0)
{
execTimes=Integer.parseInt(args[0]);
}
System.out.println("execTimes="+execTimes);
long starMs=System.currentTimeMillis();
StringBuilder strBuilder=new StringBuilder();
for(int i=0;i<execTimes;i++)
{
strBuilder.append(i);
}
long endMs=System.currentTimeMillis();
System.out.println("StringBuilder exec millis="+(endMs-starMs));
}
}

和上次一样的参数,看看执行时间的差异
C:\>java StringBuilderAppendDemo 100
execTimes=100
StringBuilder exec millis=0
C:\>java StringBuilderAppendDemo 1000
execTimes=1000
StringBuilder exec millis=1
C:\>java StringBuilderAppendDemo 10000
execTimes=10000
StringBuilder exec millis=1
C:\>java StringBuilderAppendDemo 100000
execTimes=100000
StringBuilder exec millis=5

可以看到,这里的执行次数上升以后,执行时间并没有出现大幅度的增加,那我们在看一下编译后的字节码。
51: lstore_3
52: new #5; //class java/lang/StringBuilder
55: dup
56: invokespecial #6; //Method java/lang/StringBuilder."<init>":()V
59: astore 5
61: iconst_0
62: istore 6
64: iload 6
66: iload_2
67: if_icmpge 84
70: aload 5
72: iload 6
74: invokevirtual #9; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
77: pop
78: iinc 6, 1
81: goto 64

通过字节码可以看到,整个循环拼接过程中,只在56行对StringBuilde对象进行了一次初始化,以后的拼接操作的循环都是从64行开始,然后到81行进行goto 64再次循环。

为了证明我们的推断,我们需要看看虚拟机中是否是这么实现的。

参考代码:http://www.docjar.com/html/api/com/sun/tools/javac/jvm/Gen.java.html

具体的方法,标红的地方就是在语法树处理过程中的一个用来处理字符串拼接“+”号的例子,其他部分进行的处理也类似,我们只保留需要的部分

 综上所述,如果在编写代码的过程中大量使用+进行字符串评价还是会对性能造成比较大的影响,但是使用的个数在1000以下还是可以接受的,大于10000的话,执行时间将可能超过1s,会对性能产生较大影响。如果有大量需要进行字符串拼接的操作,最好还是使用StringBuffer或StringBuilder进行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息