多次字串相加一定要用StringBuilder而不用 + 嗎?

since1986發表於2017-11-06

今天在寫一個讀取Java class File並進行分析的Demo時,偶然發現了下面這個場景(基於oracle jdk 1.8.0_144):

package test;

public class Test8 {

    String s1 = "111", s2 = "222", s3 = "333", s4 = "444";

    public String test() {
        return s1 + s2 + s3 + s4 + "5555" + "66666666666666666666666666" + "777" + new String("測試測試") + String.valueOf("test test") + "長字串長字串長字串長字串長字串長字串長字串長字串長字串長字串長字串長字串";
    }
}
複製程式碼

這是一個很簡單的類,只完成了字串的 + 操作,我們檢視對應生成的class檔案的outline:

// class version 52.0 (52)
// access flags 0x21
public class test/Test8 {

  // compiled from: Test8.java

  // access flags 0x0
  Ljava/lang/String; s1

  // access flags 0x0
  Ljava/lang/String; s2

  // access flags 0x0
  Ljava/lang/String; s3

  // access flags 0x0
  Ljava/lang/String; s4

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 5 L1
    ALOAD 0
    LDC "111"
    PUTFIELD test/Test8.s1 : Ljava/lang/String;
    ALOAD 0
    LDC "222"
    PUTFIELD test/Test8.s2 : Ljava/lang/String;
    ALOAD 0
    LDC "333"
    PUTFIELD test/Test8.s3 : Ljava/lang/String;
    ALOAD 0
    LDC "444"
    PUTFIELD test/Test8.s4 : Ljava/lang/String;
    RETURN
   L2
    LOCALVARIABLE this Ltest/Test8; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x1
  public test()Ljava/lang/String;
   L0
    LINENUMBER 8 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 0
    GETFIELD test/Test8.s1 : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD test/Test8.s2 : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD test/Test8.s3 : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD test/Test8.s4 : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "555566666666666666666666666666777"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    NEW java/lang/String
    DUP
    LDC "\u6d4b\u8bd5\u6d4b\u8bd5"
    INVOKESPECIAL java/lang/String.<init> (Ljava/lang/String;)V
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "test test"
    INVOKESTATIC java/lang/String.valueOf (Ljava/lang/Object;)Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32\u957f\u5b57\u7b26\u4e32"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this Ltest/Test8; L0 L1 0
    MAXSTACK = 4
    MAXLOCALS = 1
}

複製程式碼

請注意這段:

// access flags 0x1
  public test()Ljava/lang/String;
   L0
    LINENUMBER 8 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 0
    GETFIELD test/Test8.s1 : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
複製程式碼

我們可以看到,即便我們沒有顯式的使用StringBuilder,實際上編譯器也會隱式的將我們的 + 運算子優化為StringBuilderappend()操作;另外,其中字串常量的相加這裡,也就是 "5555" + "66666666666666666666666666" + "777" 這裡對應的操作是:

GETFIELD test/Test8.s4 : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "555566666666666666666666666666777"
複製程式碼

直接被合併在了一次(具體這是什麼操作我不是很明白)

這時候我就想起來,原來一直被教導的“字串相加一定要用StringBuilder而不要用 + ”真的正確嗎?這個值得深思。


2017-01-22更新:

GETFIELD test/Test8.s4 : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "555566666666666666666666666666777"
複製程式碼

直接被合併在了一次(具體這是什麼操作我不是很明白)

這裡可能是編譯器做了“公共子表示式消除”這個優化操作


2017-01-25更新:

在迴圈+=的情況下,編譯器也會做優化工作的,但是IDE仍然會給出警告,不知道編譯器的優化是否在所有情況下均會觸發(有待繼續學習)

public static void main(String[] args) {
    String s = "";
    for (int i = 0; i < 10000; i++) {
        int int_ = new Random().nextInt();
        s += int_;
    }
    System.out.println(s);
}
複製程式碼

多次字串相加一定要用StringBuilder而不用 + 嗎?

多次字串相加一定要用StringBuilder而不用 + 嗎?

相關文章