从jvm字节码看String+字符串拼接为什么效率低

在我们的常识里面,用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,这是比较耗时间的。

猜你喜欢

转载自blog.csdn.net/fst438060684/article/details/84233480