优秀的编程知识分享平台

网站首页 > 技术文章 正文

有了Java8的“+”真的可以不要StringBuilder了吗

nanyue 2024-07-30 03:35:34 技术文章 10 ℃


最近在头条上看到一篇帖子,说Java8开始,字符串拼接时,“+”会被编译成StringBuilder,所以,字符串的连接操作不用再考虑效率问题了,事实真的是这样吗?要搞明白,还是要看看Java编译后的字节码。

先比较这样两段代码。最简单的字符串拼接,一个用“+”,一个用StringBuilder。

    public void useOperator(){
        String a = "abc";
        String b = "efg";
        String c = a + b;
        System.out.println(c);
    }
    public void useStringBuilder(){
        String a = "abc";
        String b = "efg";
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(a);
        stringBuilder.append(b);
        System.out.println(stringBuilder.toString());
    }

用javap去看这个代码的字节码,如下:

  public void useOperator();
    Code:
       '''a和b分别被存储到局部变量1和2中'''
       0: ldc           #2                  // String abc
       2: astore_1
       3: ldc           #3                  // String efg
       5: astore_2
       '''"+"被转为StringBuilder'''
       6: new           #4                  // class java/lang/StringBuilder
       '''复制一个引用,入栈'''
       9: dup
       '''初始化StringBuilder,出栈'''
      10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
       '''取出变量a'''
      13: aload_1
       '''调用append'''
      14: invokevirtual #6                  // Method 
java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       '''取出变量b'''
      17: aload_2
       '''调用append'''
      18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       '''将toString返回的结果保存到局部变量3中,就是变量c'''
      24: astore_3
      25: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
       '''取出变量c'''
      28: aload_3
       '''打印结果'''
      29: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      32: return

对比源代码,useOperator中的c=a+b,被编译成了使用StringBuilder来操作,并依次把a和b添加到其中,看来确实jvm优化了“+”的拼接功能。

再看看useStringBuilder的字节码:

  public void useStringBuilder();
    Code:
       0: ldc           #2                  // String abc
       2: astore_1
       3: ldc           #3                  // String efg
       5: astore_2
       6: new           #4                  // class java/lang/StringBuilder
       9: dup
      10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      13: astore_3
      14: aload_3
      15: aload_1
      16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      '''append方法是带返回值的,使用invokevirtual指令,如果后面不继续使用返回结果,就需要将其pop出栈,否则后面的使用就乱了'''
      19: pop
      20: aload_3
      21: aload_2
      22: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      25: pop
      26: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      29: aload_3
      30: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      33: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      36: return

基本上和使用“+”的字节码是一致的,只不过是多了几次aload及pop,核心是一样的。

从上面的比较看,对于单一的字符串拼接,“+”确实等效于StringBuilder。能不能确认“+”是否可以替代StringBuilder,这些还不够,再看看稍微复杂一些的。

三个变量拼接。

    public void useOperator(){
        String a = "abc";
        String b = "efg";
        String c = "123";
        String e = a + b + c;
        System.out.println(e);
    }
  public void useOperator();
    Code:
       0: ldc           #2                  // String abc
       2: astore_1
       3: ldc           #3                  // String efg
       5: astore_2
       6: ldc           #4                  // String 123
       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: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      36: aload         4
      38: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      41: return

貌似也没什么问题,依旧是对同一个StringBuilder对象操作。

再改一点,两次使用“+”操作符。看看会有什么不同吗?

    public void useOperator(){
        String a = "abc";
        String b = "efg";
        String c = "123";
        String e = a + b;
        e = e + c;
        System.out.println(e);
    }
  public void useOperator();
    Code:
       0: ldc           #2                  // String abc
       2: astore_1
       3: ldc           #3                  // String efg
       5: astore_2
       6: ldc           #4                  // String 123
       8: astore_3
       '''第一个StringBuilder'''
       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: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      27: astore        4
      '''第二个StringBuilder'''
      29: new           #5                  // class java/lang/StringBuilder
      32: dup
      33: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
      ......
      58: return

我们注意第9行和第29行,分别对应源码的下面两行。

String e = a + b;
e = e + c;

这两句,竟然分别创建了一个StringBuilder,如果你再多写几个“+”操作,就会多创建几个StringBuilder,也即是说,每个“+”的出现,都会有一个StringBuilder被new出来,这个开销实在太大了。由此看来“+”还是不能完全替代StringBuilder,只能在极简情况下可以这样理解。

知道了这个结果,那我们就应该明白,假如你有一个for(或while)循环,里面有字符串的拼接操作,你应该使用“+”还是使用StringBuilder呢?

Tags:

最近发表
标签列表