【Java】String的重载"+"操作与StringBuider

在Java当中,String类中的每一个看上去会修改String值的方法,实际上都会创建一个全新的String对象,以包含修改后字符串的内容。

String a = "Hello";
a = a + ", World";

代码中第一行的a和第二行的a指向的是两个不同的物理位置。


String对象是不可变的,他具有只读的属性,所以指向它的任何引用都不可能改变他的值,因此也就不会对其他的引用产生影响

String a = "Hello";
String b = a;
a = "World";
System.out.println(a);
System.out.println(b);

运行结果:

World
Hello

可以看到指向a的改变并没有影响到b的值


可变性会带来一定效率上的问题,String对象重载的”+”操作符就是一个例子。

例如String b = "abc " + a + " def " + 55;
这段代码可能是这样工作的:
String可能首先创建"abc "这个字符串,之后与a相连接创建"abc " + a ,再与" def "相连生成新的String对象,以此类推……

public class Concatenation {
    public static void main(String...args) {
        String a = "Hello";
        // 编译器真的会傻傻的生成大量的中间对象吗?
        String b = "abc " + a + " def " + 55;
        System.out.println(b);
    }
}

这种创建方式虽然行得通,但是为了生成最终的String,产生了一大堆需要垃圾回收的中间对象。那么编译器该如何对以上代码进行优化呢?我们使用JDK自带的工具javap来反编译以上代码:
javap -c Concatenation
得到如下的字节码(Bytecode):

λ javap -c Concatenation
警告: 二进制文件Concatenation包含String.Concatenation
Compiled from "Concatenation.java"
public class String.Concatenation {
  public String.Concatenation();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String...);
    Code:
       0: ldc           #2                  // String Hello
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #5                  // String abc
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_1
      16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: ldc           #7                  // String  def
      21: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: bipush        55
      26: invokevirtual #8                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      29: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      32: astore_2
      33: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      36: aload_2
      37: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      40: return
}

从第3行可以看出编译器自动引入了java.lang.StringBuider类。虽然在我们的源码中没有使用StringBuider,但是编译器为了优化代码自动使用了该类。

本例中,编译器创建了一个StringBuilder对象,用来构造最终的String,并为每个字符串调用append()方法,最后调用toString(),这样做远比生成一大堆中间类要高效得多。


所以我们是不是就可以随便使用String对象咯,反正编译器会自动帮你优化性能?

答案是否定的。我们来看看编译器能为我们优化到什么地步:

public class WhetherStringBuilder {
    public String implicit(String[] args) {
        String result = "";
        for (int i = 0; i < args.length; i++)
            result += args[i];
        return result;
    }

    public String explicit(String[] args) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < args.length; i++)
            result.append(args[i]);
        return result.toString();
    }
}

同样使用javap得到如下字节码:

λ javap -c WhetherStringBuilder
警告: 二进制文件WhetherStringBuilder包含String.WhetherStringBuilder
Compiled from "WhetherStringBuilder.java"
public class String.WhetherStringBuilder {
  public String.WhetherStringBuilder();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public java.lang.String implicit(java.lang.String[]);
    Code:
       0: ldc           #2                  // String
       2: astore_2
       3: iconst_0
       4: istore_3
       5: iload_3
       6: aload_1
       7: arraylength
       8: if_icmpge     38
      11: new           #3                  // class java/lang/StringBuilder
      14: dup
      15: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      18: aload_2
      19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      22: aload_1
      23: iload_3
      24: aaload
      25: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      28: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      31: astore_2
      32: iinc          3, 1
      35: goto          5
      38: aload_2
      39: areturn

首先是implicit(),从第8到第35行构成了一个循环体,这个循环体每run一次,就会创建一个新的StringBuilder对象

  public java.lang.String explicit(java.lang.String[]);
    Code:
       0: new           #3                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
       7: astore_2
       8: iconst_0
       9: istore_3
      10: iload_3
      11: aload_1
      12: arraylength
      13: if_icmpge     30
      16: aload_2
      17: aload_1
      18: iload_3
      19: aaload
      20: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      23: pop
      24: iinc          3, 1
      27: goto          10
      30: aload_2
      31: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      34: areturn
}

explicit()方法的字节码就很简单了,自始至终只创建了一个StringBullder,并不断地进行append()操作


注意:类似append(a + ":" + b)的语句,编译器会另外创建一个StringBuilder来处理括号内的字符串操作,有兴趣的读者可以自己尝试验证一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值