《提高String和StringBuffer性能的技巧》一文的后续讨论

提高String和StringBuffer性能的技巧》一文中提到一个遗留的疑问:
虽然使用+操作符连接字符串的时候+操作符在编译以后会优化地被StringBuffer接管,但是,在大数量级的循环中以+操作符连接字符串的时候为什么效率还是非常低呢?现鄙人将问题解答如下。

首先我们看看两段程序:
Test1.java
public class Test1{
    public static void main(String args[]){
        StringBuffer sb = new StringBuffer();
        String sRes = null;
        for(int i = 0 ; i < 1024*1024; i++ ){
            sb.append( "XXX" );
        }
        sRes = sb.toString();
    }
}
上面这段代码的执行时间在0.1秒钟以内。

Test2.java
public class Test2{
    public static void main(String args[]){
        //StringBuffer sb = new StringBuffer();
        String sRes = null;
        for(int i = 0 ; i < 1024*1024; i++ ){
            sRes += "XXX" ;
        }
    }
}
而Test2.java这段代码的执行时间估计会超过10分钟(我等了两三分钟不耐烦就强行咔嚓了。)

Well,我们再看看两段代码分别编译后的伪代码,便可以发现线索。
C:/temp>javap -c Test1
Compiled from "Test1.java"
public class Test1 extends java.lang.Object{
public Test1();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   new     #2; //class StringBuffer
   3:   dup
   4:   invokespecial   #3; //Method java/lang/StringBuffer."<init>":()V
   7:   astore_1
   8:   aconst_null
   9:   astore_2
   10:  iconst_0
   11:  istore_3
   12:  iload_3
   13:  ldc     #4; //int 1048576
   15:  if_icmpge       31
   18:  aload_1
   19:  ldc     #5; //String XXX
   21:  invokevirtual   #6; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   24:  pop
   25:  iinc    3, 1
   28:  goto    12
   31:  aload_1
   32:  invokevirtual   #7; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
   35:  astore_2
   36:  return
}
对于Test1,我们主要关心0、12、28这三行伪码,我们可以发现StringBuffer类的创建是在循环开始之前,也就是说,从程序执行的开始到结束,StringBuffer类的实例始终只有一个。

C:/temp>javap -c Test2
Compiled from "Test2.java"
public class Test2 extends java.lang.Object{
public Test2();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   aconst_null
   1:   astore_1
   2:   iconst_0
   3:   istore_2
   4:   iload_2
   5:   ldc     #2; //int 1048576
   7:   if_icmpge       36
   10:  new     #3; //class StringBuffer
   13:  dup
   14:  invokespecial   #4; //Method java/lang/StringBuffer."<init>":()V
   17:  aload_1
   18:  invokevirtual   #5; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   21:  ldc     #6; //String XXX
   23:  invokevirtual   #5; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   26:  invokevirtual   #7; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
   29:  astore_1
   30:  iinc    2, 1
   33:  goto    4
   36:  return
}
对于Test2,我们主要关心4、10、33这三行伪码,我们可以发现StringBuffer类的创建是在循环体的内部,这很恐怖--从程序执行的开始到结束,StringBuffer类的实例将被不停地创建并被GC,这样将导致大量的CPU和内存开销。对此我特地用Borland的Optimization Suit中的Profile工具观察过这段程序执行过程中的各种类型的实例数量、CPU负载、内存负载、GC动作等变化现象,可以实证上面的解释,大家有兴趣的话也不妨验证一下。

最后,我们可以得出一个结论:需要在循环中连接运行期决定的String实例的时候,请使用StringBuffer类提供的字符串连接功能来代替+操作符。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值