從jvm位元組碼看String+字串拼接為什麼效率低
阿新 • • 發佈:2018-11-26
在我們的常識裡面,用String的+符號也就是字串的時候,效率會很低,建議使用String builder,那是為什麼呢?這次我們通過一個小demo的jvm位元組碼來分析,首先是demo:
public class TestStringAdd { public static void f1() { String src = ""; for(int i=0;i<10;i++) { //每一次迴圈都會new一個StringBuilder src = src + "A"; } System.out.println(src); } public static void f2() { //只要一個StringBuilder StringBuilder src = new StringBuilder(); for(int i=0;i<10;i++) { src.append("A"); } System.out.println(src); } }
解析成位元組碼之後,就是這樣子的:
Classfile /G:/WorkingProjects/learn/project/test/out/production/test/TestStringAdd.class Last modified 2018-11-19; size 991 bytes MD5 checksum a8627bc62f24056ffb5ed04ca4386ac2 Compiled from "TestStringAdd.java" public class TestStringAdd minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #12.#32 // java/lang/Object."<init>":()V #2 = String #33 // #3 = Class #34 // java/lang/StringBuilder #4 = Methodref #3.#32 // java/lang/StringBuilder."<init>":()V #5 = Methodref #3.#35 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #6 = String #36 // A #7 = Methodref #3.#37 // java/lang/StringBuilder.toString:()Ljava/lang/String; #8 = Fieldref #38.#39 // java/lang/System.out:Ljava/io/PrintStream; #9 = Methodref #40.#41 // java/io/PrintStream.println:(Ljava/lang/String;)V #10 = Methodref #40.#42 // java/io/PrintStream.println:(Ljava/lang/Object;)V #11 = Class #43 // TestStringAdd #12 = Class #44 // java/lang/Object #13 = Utf8 <init> #14 = Utf8 ()V #15 = Utf8 Code #16 = Utf8 LineNumberTable #17 = Utf8 LocalVariableTable #18 = Utf8 this #19 = Utf8 LTestStringAdd; #20 = Utf8 f1 #21 = Utf8 i #22 = Utf8 I #23 = Utf8 src #24 = Utf8 Ljava/lang/String; #25 = Utf8 StackMapTable #26 = Class #45 // java/lang/String #27 = Utf8 f2 #28 = Utf8 Ljava/lang/StringBuilder; #29 = Class #34 // java/lang/StringBuilder #30 = Utf8 SourceFile #31 = Utf8 TestStringAdd.java #32 = NameAndType #13:#14 // "<init>":()V #33 = Utf8 #34 = Utf8 java/lang/StringBuilder #35 = NameAndType #46:#47 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #36 = Utf8 A #37 = NameAndType #48:#49 // toString:()Ljava/lang/String; #38 = Class #50 // java/lang/System #39 = NameAndType #51:#52 // out:Ljava/io/PrintStream; #40 = Class #53 // java/io/PrintStream #41 = NameAndType #54:#55 // println:(Ljava/lang/String;)V #42 = NameAndType #54:#56 // println:(Ljava/lang/Object;)V #43 = Utf8 TestStringAdd #44 = Utf8 java/lang/Object #45 = Utf8 java/lang/String #46 = Utf8 append #47 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #48 = Utf8 toString #49 = Utf8 ()Ljava/lang/String; #50 = Utf8 java/lang/System #51 = Utf8 out #52 = Utf8 Ljava/io/PrintStream; #53 = Utf8 java/io/PrintStream #54 = Utf8 println #55 = Utf8 (Ljava/lang/String;)V #56 = Utf8 (Ljava/lang/Object;)V { public TestStringAdd(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LTestStringAdd; public static void f1(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=0 0: ldc #2 // String 2: astore_0 3: iconst_0 4: istore_1 5: iload_1 6: bipush 10 8: if_icmpge 37 11: new #3 // class java/lang/StringBuilder 14: dup 15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 18: aload_0 19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: ldc #6 // String A 24: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 27: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 30: astore_0 31: iinc 1, 1 34: goto 5 37: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 40: aload_0 41: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 44: return LineNumberTable: line 3: 0 line 4: 3 line 6: 11 line 4: 31 line 8: 37 line 9: 44 LocalVariableTable: Start Length Slot Name Signature 5 32 1 i I 3 42 0 src Ljava/lang/String; StackMapTable: number_of_entries = 2 frame_type = 253 /* append */ offset_delta = 5 locals = [ class java/lang/String, int ] frame_type = 250 /* chop */ offset_delta = 31 public static void f2(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=0 0: new #3 // class java/lang/StringBuilder 3: dup 4: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 7: astore_0 8: iconst_0 9: istore_1 10: iload_1 11: bipush 10 13: if_icmpge 29 16: aload_0 17: ldc #6 // String A 19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: pop 23: iinc 1, 1 26: goto 10 29: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 32: aload_0 33: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 36: return LineNumberTable: line 12: 0 line 13: 8 line 14: 16 line 13: 23 line 16: 29 line 17: 36 LocalVariableTable: Start Length Slot Name Signature 10 19 1 i I 8 29 0 src Ljava/lang/StringBuilder; StackMapTable: number_of_entries = 2 frame_type = 253 /* append */ offset_delta = 10 locals = [ class java/lang/StringBuilder, int ] frame_type = 250 /* chop */ offset_delta = 18 } SourceFile: "TestStringAdd.java"
我們先看下幾個指令的意思,這裡中文解釋很好:https://www.cnblogs.com/tenghoo/p/jvm_opcodejvm.html
ldc #2:命令負責把數值常量或String常量值(#2)從常量池中推送至棧頂。
if_icmpge 37:意思就是如果相比大於,那就跳到第37行,否則繼續往下執行。最後將棧裡面的這兩個元素移除。
dup: 複製棧頂數值(數值不能是long或double型別的)並將複製值壓入棧頂
goto 5:跳到第5行
有了這些前提知識之後,我們可以知道,在本篇文章中的String在呼叫+之前都會new StringBuilder,然後才append,這是比較耗時間的。