字符串连接性能:+ vs StringBuilder

String concatenation performance: + vs StringBuilder

我正在检查 this similar post ,其中的最佳答案说 +StringBuilder.append() 之间“没有任何性能差异”,因为前者将变成后者JVM.
然而,根据我的基准测试,+ 方式总是比 StringBuilder 方式快约 20%(我在 Java17、运行 Intel core i7 上):

    @State(Scope.Thread)
    public static class BenchState {
        private String a = "A";
        private String b = "B";
        private String c = "C";
    }

    @Benchmark
    public void bmStringConcat(final BenchState state, final Blackhole blackhole) {
        String a = state.a;
        String b = state.b;
        String c = state.c;

        final String s = "{a:" + a + ", b:" + b + ", c: " + c + "}";
        blackhole.consume(s);
    }

    @Benchmark
    public void bmStringBuilder(final BenchState state, final Blackhole blackhole) {
        String a = state.a;
        String b = state.b;
        String c = state.c;
        StringBuilder sb = new StringBuilder();
        final String s = sb.append("{a:").append(a)
                .append(", b:").append(b)
                .append(", c:").append(c)
                .append("}")
                .toString();
        blackhole.consume(s);
    }

是不是因为上面提到的“+”版本“转换为invokedynamic调用”here.
还是有更多的原因?

听从@Holger 的建议,我检查了以下代码的字节码:

public class StringBM {
    public String toStringPlus(String a) {
        return "{a:" + a + ", b:" + ", c: " + "}";
    }

    public String toStringBuilder(String a) {
        StringBuilder sb = new StringBuilder(100);
        return sb.append("{a:").append(a)
                .append(", b:")
                .append(", c:")
                .append("}")
                .toString();
    }
}

对于toStringPlus,我得到

  public java.lang.String toStringPlus(java.lang.String);
    descriptor: (Ljava/lang/String;)Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_1
         1: invokedynamic #7,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
         6: areturn

对于 toStringBuilder:

public java.lang.String toStringBuilder(java.lang.String);
    descriptor: (Ljava/lang/String;)Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=2
         0: new           #11                 // class java/lang/StringBuilder
         3: dup
         4: bipush        100
         6: invokespecial #13                 // Method java/lang/StringBuilder."<init>":(I)V
         9: astore_2
        10: aload_2
        11: ldc           #16                 // String {a:
        13: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        16: aload_1
        17: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: ldc           #22                 // String , b:
        22: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        25: ldc           #24                 // String , c:
        27: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        30: ldc           #26                 // String }
        32: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        35: invokevirtual #28                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        38: areturn

+版本只是调用动态函数makeConcatWithConstants
StringBuilder 版本必须以 'honest' 方式进行。
我想我们可以明白为什么 + 现在更快了。