String拼接字符串底层原理

JDK版本:1.8

先上结果:

原理:jdk1.8之后字符串拼接底层就是创建了一个StringBuilder,然后调用append方法,最后调用toString转化成String

结论:java9之前,StringBuilder的append方法效率永远大于用+拼接,且拼接次数越多,差距越大!

(9之后呢?https://blog.csdn.net/qq_35425070/article/details/90718963)

下面是特殊情况和通常情况的底层探究:

实际代码:

public class aaa {
	public static void main(String[] args) {
		String a = "abc";
		a = a + "d";
	}
} 

编译器之后的等效代码:

public class aaa {
	public static void main(String[] args) {
		String a = "abc";
		StringBuilder temp = new StringBuilder(a);
		temp.append("d");
		a = temp.toString();
	}
} 

Why?

来看javap之后的字节码,下面仅贴上主函数:


  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: ldc           #16                 // String abc
         2: astore_1
         3: new           #18                 // class java/lang/StringBuilder
         6: dup
         7: aload_1
         8: invokestatic  #20                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        11: invokespecial #26                 // Method java/lang/StringBuilder."":(Ljava/lang/String;)V
        14: ldc           #29                 // String d
        16: invokevirtual #31                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #35                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: astore_1
        23: return
      LineNumberTable:
        line 7: 0
        line 8: 3
        line 9: 23
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0  args   [Ljava/lang/String;
            3      21     1     a   Ljava/lang/String;
}

可以看到2行的java代码转换成字节码之后有23行,为了让小白也看懂,先来解释一下命令,

ldc 将常量池中的常量值"abc"入栈

astore_1 把栈顶引用类型值保存到变量1,(这里变量1就是a,思考那变量0是什么,看底下本地变量表发现变量0是函数的参数 args,特意把底下变量表也贴上了)

new   创建一个新的对象,从字节码的注释中可以看出编译器在这帮我们new了一个StingBuilde类型的对象

dup  复制栈顶一个字长的数据,将复制后的数据压栈。

aload_1  从局部变量1中装载引用类型值入栈。

invokestatic   调用静态方法。从注释中可以看出调用了String类的ValueOf(Object obj)方法,返回值是String类型,这个是由于参数不匹配时候自动调用的,哪里不匹配了?继续往下看,答案就在下一行。

 11: invokespecial #26                 // Method java/lang/StringBuilder."":(Ljava/lang/String;)V

这里调用了StringBuilder的构造方法,哪个对象调用的呢?就是第3行new的时候那个Stringbuilder对象调用的,到源码里看一下是哪个方法究竟,eclipse 里按 ctrl + o 发现了这个方法,他的参数需要一个str类型的变量,你给我的是object类型,聪明的jvm编辑器帮我们调用了一次上面提到的String.ValueOf(Object obj):上一行的结果作为这一行的参数。

String拼接字符串底层原理_第1张图片
        14: ldc           #29                 // String d

将常量池中的常量值"d"入栈
        16: invokevirtual #31                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

invokevirtual    运行时方法绑定调用方法。从注释中可以看出来,是调用的StringBuilder.append:(Ljava/lang/String;)方法,贴上jdk源码:

String拼接字符串底层原理_第2张图片

即调用了append方法
        19: invokevirtual #35                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

invokevirtual还是运行时方法绑定调用方法。从注释中可以看出来,是调用的StringBuilder.toString:()方法,贴上jdk源码:

String拼接字符串底层原理_第3张图片
        22: astore_1

将栈顶引用类型值保存到局部变量1中。这里局部变量1 查局部变量表可以看到是a,即把刚才toString的值赋值给了a(注意是引用)
        23: return

方法执行完毕,return

另附我改写的+号拼接java字节码:


  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=3, args_size=1
         0: ldc           #16                 // String abc
         2: astore_1
         3: new           #18                 // class java/lang/StringBuilder
         6: dup
         7: aload_1
         8: invokespecial #20                 // Method java/lang/StringBuilder."":(Ljava/lang/String;)V
        11: astore_2
        12: aload_2
        13: ldc           #23                 // String d
        15: invokevirtual #25                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        18: pop
        19: aload_2
        20: invokevirtual #29                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        23: astore_1
        24: return
      LineNumberTable:
        line 7: 0
        line 8: 3
        line 9: 12
        line 10: 19
        line 11: 24
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      25     0  args   [Ljava/lang/String;
            3      22     1     a   Ljava/lang/String;
           12      13     2  temp   Ljava/lang/StringBuilder;
}

可以看到java8对+拼接字符串做了一定的优化。

想继续深入探讨的下面仅仅举几个例子,自己编译查看:

public class aaa {
//第一种写法
/*	public static void main(String[] args) {
		String a = "abc";
		StringBuilder temp = new StringBuilder(a);
		temp.append("d");
		temp.append("e");
		temp.append("f");
		temp.append("g");
		a = temp.toString();
	}*/
//第二种写法
	/*public static void main(String[] args) {
		String a = "abc";
		a = a + "d";
		a = a + "e";
		a = a + "f";
		a = a + "g";
	}*/
//第三种,特意写出来,没有可比性,实际中不会有人这么写,因为这样写相当于 a = a + "defg".
	public static void main(String[] args) {
		String a = "abc";
		a = a + "d" + "e" + "f" + "g";
	}
} 

其中推荐第一种写法,查看java字节码发现字节码只有45行,而用拼接方式,字节码长度达到了83行,如果拼接的次数更多,执行的效率差距将更大。

所以尽管jdk8对+做了优化,但还是用Stringbuilder的append方法效率更高。

 

更多java字节码解释:https://blog.csdn.net/qq_35425070/article/detils/84248082

你可能感兴趣的:(java,底层原理)