String拼接字符串底层原理 £神魔★判官ぃ 2022-04-15 00:15 203阅读 0赞 **JDK版本:1.8** ### 先上结果: ### ### 原理:jdk1.8之后字符串拼接底层就是创建了一个StringBuilder,然后调用append方法,最后调用toString转化成String ### # 结论:java9之前,StringBuilder的append方法效率永远大于用+拼接,且拼接次数越多,差距越大! # (9之后呢?[https://blog.csdn.net/qq\_35425070/article/details/90718963][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."<init>":(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类型,这个是由于参数不匹配时候自动调用的,哪里不匹配了?继续往下看,答案就在下一行。![20181119143825530.png][] 11: invokespecial \#26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 这里调用了StringBuilder的构造方法,哪个对象调用的呢?就是第3行new的时候那个Stringbuilder对象调用的,到源码里看一下是哪个方法究竟,eclipse 里按 ctrl + o 发现了这个方法,他的参数需要一个str类型的变量,你给我的是object类型,聪明的jvm编辑器帮我们调用了一次上面提到的String.ValueOf(Object obj):上一行的结果作为这一行的参数。 ![2018111914424812.png][] 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源码: ![20181119144618291.png][] 即调用了append方法 19: invokevirtual \#35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; invokevirtual还是运行时方法绑定调用方法。从注释中可以看出来,是调用的StringBuilder.toString:()方法,贴上jdk源码: ![20181119144850451.png][] 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."<init>":(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][https_blog.csdn.net_qq_35425070_article_detils_84248082] [https_blog.csdn.net_qq_35425070_article_details_90718963]: https://blog.csdn.net/qq_35425070/article/details/90718963 [20181119143825530.png]: /images/20220415/8b5176ab9d7c43b3ba6a7b702d2f2e99.png [2018111914424812.png]: /images/20220415/cb00836c61e940f6b978363945635c6f.png [20181119144618291.png]: /images/20220415/0a5cc28d936949eb84775920c17e5858.png [20181119144850451.png]: /images/20220415/61d70113d51347439ad54e8bef790b9b.png [https_blog.csdn.net_qq_35425070_article_detils_84248082]: https://blog.csdn.net/qq_35425070/article/details/84248082
还没有评论,来说两句吧...