用StringBuilder(StringBuffer)#append替代字符串”+”会带来性能提升吗(转)

3

经常看到一些论坛在谈java代码优化的时候讲到要将字符串连接操作”+”换成StringBuilder(或StringBuffer,后面为简单起见,只说StringBuilder)的append操作以提升性能,那么字符串连接使用StringBuilder#append来替代”+”真的会带来性能提升吗?

不忙回答,先看几个例子,代码如下:

public class StringConcat {
     public static void main(String... args) {
         concat1();
         concat2();
         concat3();
     }
  
     public static void concat1() {
         String s = "today is " + "a good day" ;
         System.out.println(s);
     }
  
     public static void concat2() {
         int count = 2 ;
         String tmp = " on the desk" ;
         String s2 = "there are " + count + " books " + tmp;
         System.out.println(s2);
     }
  
     public static void concat3() {
         String s3 = "" ;
         for ( int i= 0 ; i< 100 ; i++) {
             s3 = s3 + i;
         }
         System.out.println(s3);
     }
}

接下来分别分析下这三个操作字符串的方法,通过javap命令反编译.class文件:javap -c StringConcat ,获得字节码指令如下(只摘取concat1,concat2,concat3三个方法的):

public static void concat1();
   Code:
    0 :   ldc     # 5 ; //String today is a good day
    2 :   astore_0
    3 :   getstatic       # 6 ; //Field java/lang/System.out:Ljava/io/PrintStream;
    6 :   aload_0
    7 :   invokevirtual   # 7 ; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    10 return
  
public static void concat2();
   Code:
    0 :   iconst_2
    1 :   istore_0
    2 :   ldc     # 8 ; //String  on the desk
    4 :   astore_1
    5 :   new     # 9 ; //class java/lang/StringBuilder
    8 :   dup
    9 :   invokespecial   # 10 ; //Method java/lang/StringBuilder."<init>":()V
    12 :  ldc     # 11 ; //String there are
    14 :  invokevirtual   # 12 ; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    17 :  iload_0
    18 :  invokevirtual   # 13 ; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
    21 :  ldc     # 14 ; //String  books
    23 :  invokevirtual   # 12 ; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    26 :  aload_1
    27 :  invokevirtual   # 12 ; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    30 :  invokevirtual   # 15 ; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    33 :  astore_2
    34 :  getstatic       # 6 ; //Field java/lang/System.out:Ljava/io/PrintStream;
    37 :  aload_2
    38 :  invokevirtual   # 7 ; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    41 return
  
public static void concat3();
   Code:
    0 :   ldc     # 16 ; //String
    2 :   astore_0
    3 :   iconst_0
    4 :   istore_1
    5 :   iload_1
    6 :   bipush  100
    8 :   if_icmpge       36
    11 new     # 9 ; //class java/lang/StringBuilder
    14 :  dup
    15 :  invokespecial   # 10 ; //Method java/lang/StringBuilder."<init>":()V
    18 :  aload_0
    19 :  invokevirtual   # 12 ; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    22 :  iload_1
    23 :  invokevirtual   # 13 ; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
    26 :  invokevirtual   # 15 ; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    29 :  astore_0
    30 :  iinc    1 , 1
    33 goto    5
    36 :  getstatic       # 6 ; //Field java/lang/System.out:Ljava/io/PrintStream;
    39 :  aload_0
    40 :  invokevirtual   # 7 ; //Method java/io/PrintStream.println:(Ljava/lang/String;)V

来分析下三个方法的字节码含义

在concat1中,是两个字面值(字符串常量)的连接,从concat1的字节码的第0条(0: ldc #5; //String today is a good day)可以看到,该方法直接从常量池加载”String today is a good day”,也就是说,String s = “today is ” + “a good day”;这条语句在编译后已经变成了一个字符串,等效于String s = “String today is a good day”,运行期间根本无需做连接操作了,所以对于字符串字面值的连接,使用StringBuilder是没有任何意义的

在concat2中,是变量参与字符串的连接。从反编译的字节码中可以看出,编译期间已经转换成了StringBuilder的append操作,

String s2 = "there are " + count + " books " + tmp;

语句在编译之后已经等效于(即[5,30]之间的指令):

String s2 = new StringBuilder().append( "there are " ).append(count).append( " books" ).append(tmp).toString();

由此可见,在这样的字符串连接代码里显式使用new StringBuilder().append并不会带来性能的提升,因为String的“+”操作符在编译的时候已经被转换成new StringBuilder().append了。当然,StringBuilder可以传入一个int参数,作为其初始容量,这在生成的代码中是没法做到的,而只有程序可以控制。

最后看concat3,for循环中使用字符串连接,最后在for循环外使用连接后的字符串。字节码中的[11,29]之间是循环体,很容易发现,循环体中做了new StringBuilder的操作,字节码代表的代码含义如下:

String s3 = "" ;
for ( int i= 0 ; i< 100 ; i++) {
     s3 = new StringBuilder().append(s3).append(i).toString();
}

在这种情况下,编译器的优化并不如我们的意,我们想要的优化代码是这样的:

String s3 = "" ;
StringBuilder tmp = new StringBuilder();
tmp.append(s3);
for ( int i= 0 ; i< 100 ; i++) {
     tmp.append(i);
}
s3 = tmp.toString();

这对于编译器来说有些复杂了,我们需要手工才能做到。

综上三个方法的分析发现,用StringBuilder(StringBuffer)#append替代字符串”+”是否会带来性能提升并不是一成不变的,在不同的条件下情况也不相同,字符串字面值的连接在编译期间已经连接好了,普通的字符串连接并不需要显式的使用new StringBuilder().append来增加效率,编译器已经给我们做掉了,除非能在显式使用时能给出有效的初始容量。在这种意义下,个人觉得string的”+”可以认为是StringBuilder#append的一个语法糖;但是如果形如concat3那种循环中的字符串连接,我们就需要显式使用StringBuilder了。在jdk1.4的时候,还没有StringBuilder类,编译器生成的优化代码使用的是StringBuffer。

针对String连接操作编译器生成的StringBuidler#append肯定是单个线程在操作,因此不会有线程安全问题。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值