StringBuilder vs Java中toString()的字符串连接

non*_*tor 889 java string performance stringbuilder concatenation

鉴于toString()下面的两个实现,首选哪一个:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}
Run Code Online (Sandbox Code Playgroud)

要么

public String toString(){
    StringBuilder sb = new StringBuilder(100);
    return sb.append("{a:").append(a)
          .append(", b:").append(b)
          .append(", c:").append(c)
          .append("}")
          .toString();
}
Run Code Online (Sandbox Code Playgroud)

更重要的是,鉴于我们只有3个属性,它可能没有什么区别,但你会在什么时候从+concat 切换到 StringBuilder

Mic*_*rdt 929

版本1更可取,因为它更短,编译器实际上将其转换为版本2 - 无论如何都没有性能差异.

更重要的是,鉴于我们只有3个属性,它可能没有什么区别,但你在什么时候从concat切换到builder?

在循环中连接的时候 - 这通常是编译器无法自行替换StringBuilder的时候.

  • @Lucas:不,我们不应该.如果编译器决定不执行该优化,那将是因为它不值得.在99%的情况下,编译器更清楚哪种优化是值得的,因此根据经验,开发不应该干扰.当然,您的情况*可能会落入另一个1%,但这只能通过(仔细)基准测试来检查. (91认同)
  • 不要打败死马,但规范中的措辞是:`为了提高重复字符串连接的性能,Java编译器_may_使用StringBuffer类或类似技术来减少评估创建的中间String对象的数量一个表达.关键词有_may_.鉴于这是官方可选的(尽管很可能实施)我们应该保护自己吗? (77认同)
  • 我刚刚尝试了问题中的代码(在JDK 1.6.0_16上编译)并找到了预期的优化.我很确定所有现代编译器都会这样做. (37认同)
  • 你是对的.查看字节码我可以清楚地看到StringBuilder的优化.我正在使用反编译器,有些如何,它正在转换回concat.+1 (21认同)
  • 这是真的,但语言参考也表明这是可选的.事实上,我只是用JRE 1.6.0_15做了一个简单的测试,我没有在反编译类中看到任何编译器优化. (19认同)
  • @sleske,我不认为你是对的.编译器查找可能的优化的能力有限.它不会为你做思考. (14认同)
  • @Vach:规范实际上说"可能使用StringBuffer类或类似的技术".提及StringBuffer有点不合时宜(特别是因为它在Java 8的当前JLS中仍然没有改变),但仅此而已.此外,如果现代JVM可以确定对象永远不会被不同的线程访问,则它通常可以消除来自同步代码的锁. (5认同)
  • 请注意,从 Java 9 开始,如果您只是连接单个“String”而不进行重复连接,则使用 + 连接可以变成比您可以手写的任何东西更好的东西,例如通过访问外部代码可以访问的“String”内部结构。去不了。这是 [JEP 280](https://openjdk.java.net/jeps/280) 和默认使用 [mh_inline_sized_exact](http://cr.openjdk.java.net/~shade/8085796 /notes.txt)策略。(这也很好地说明了为什么您应该相信编译器会做正确的事情。) (5认同)
  • 我不认为这总是如此.我最近通过删除所有"+"并用字符串构建器替换它们来优化循环中的一段代码,性能从20秒内的约500次迭代到5秒内的35,000次迭代.我对这种差异感到非常震惊.我还用String.format调用替换了一些整数的连接,这可能会增加性能; 说实话,我不确定哪个变化影响最大.但假设"+"总是很好就是一个错误. (3认同)
  • 我相信在使用StringBuilder与串联(在循环之外)的优化中,还有一个考虑因素是您是否要构造多个String。编译器是否足够聪明,可以重用它通过setLength(0)添加的StringBuilder?如果不是,那么您可以手动执行此操作,而不是通过编译器优化串联,但是缺少重新使用StringBuilder的优化。 (2认同)
  • 字节代码可以在这里看到:http://bpaste.net/show/Z8o0hdiCerkFUXhaeU7L/请注意行号不匹配,但它*是*正确的代码 (2认同)
  • @Laurent [博客文章](http://www.pellegrino.link/2015/08/22/string-concatenation-with-java-8.html) 值得一读。它解释了为什么 `StringBuilder` 更快,我们是否需要在 Java 8 等中使用它......详细说明。强烈推荐。 (2认同)

joe*_*ely 240

关键是你是在一个地方写一个连接还是随着时间累积它.

对于您给出的示例,明确使用StringBuilder没有意义.(查看第一个案例的编译代码.)

但是如果你在循环中构建一个字符串,请使用StringBuilder.

为了澄清,假设hugeArray包含数千个字符串,代码如下:

...
String result = "";
for (String s : hugeArray) {
    result = result + s;
}
Run Code Online (Sandbox Code Playgroud)

与以下相比,这是非常浪费时间和内存的:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();
Run Code Online (Sandbox Code Playgroud)

  • 该死的我用这两个函数来测试我正在使用的大字符串.6.51min vs 11secs (113认同)
  • 是的,StringBuilder不需要反复重新创建String对象. (2认同)
  • 怎么样: String str = (a == null) ?null : a' + (b == null) ? null : b' + (c == null) ? c : c' + ...; ?这会阻止优化的发生吗? (2认同)

tan*_*ens 74

我更喜欢:

String.format( "{a: %s, b: %s, c: %s}", a, b, c );
Run Code Online (Sandbox Code Playgroud)

...因为它简短易读.

除非你在具有非常高重复次数的循环中使用它测量了性能差异,否则我不会对速度进行优化.

我同意,如果你必须输出很多参数,这个表格会让人感到困惑(就像其中一条评论所说).在这种情况下,我将切换到一个更易读的形式(可能使用apache-commons的ToStringBuilder - 取自matt b的答案)并再次忽略性能.

  • 似乎更难阅读(对我而言).现在我必须在{...}和参数之间来回扫描. (74认同)
  • 它实际上更长,包含更多符号,并且变量文本不按顺序排列. (59认同)
  • 我更喜欢这种形式,因为如果其中一个参数为"null"则是安全的 (9认同)
  • 所以你会说它的可读性低于其他一个方法吗? (4认同)
  • 我更喜欢写这个,因为添加更多变量更容易,但我不确定它是否更具可读性 - 特别是当参数的数量变大时.当您需要在不同时间添加位时,它也不起作用. (3认同)
  • @rds Nope,如果`b`为null,则简单连接`a + b`与`format`完全相同.两种方法都会导致追加字符串"null". (2认同)
  • @Navin确实.`null +"issimo"`生成"nullissimo". (2认同)
  • 与问题的选项1相比,这更难理解,因为有更多的字符要读取,并且理解格式函数还有额外的复杂性(与文字串联相比). (2认同)

Omr*_*dan 71

在大多数情况下,您不会看到两种方法之间的实际差异,但很容易构建像这样的最坏情况:

public class Main
{
    public static void main(String[] args)
    {
        long now = System.currentTimeMillis();
        slow();
        System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");

        now = System.currentTimeMillis();
        fast();
        System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
    }

    private static void fast()
    {
        StringBuilder s = new StringBuilder();
        for(int i=0;i<100000;i++)
            s.append("*");      
    }

    private static void slow()
    {
        String s = "";
        for(int i=0;i<100000;i++)
            s+="*";
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

slow elapsed 11741 ms
fast elapsed 7 ms
Run Code Online (Sandbox Code Playgroud)

问题是,+ =追加到一个字符串会重建一个新字符串,因此它会花费与字符串长度成线性的东西(两者的总和).

所以 - 对你的问题:

第二种方法会更快,但它的可读性和维护难度更低.正如我所说,在你的具体情况下,你可能不会看到差异.

  • 虽然你说得对`+ =`,原来的例子是,'+'的序列,编译器转变为一个单一的`string.concat`电话.您的结果不适用. (8认同)
  • 你知道他的toString()不是在循环中调用的吗? (3认同)

per*_*ain 27

我还与我的老板发生冲突,关于是否使用append或+.他们正在使用Append(我仍然无法弄清楚他们每次创建新对象时都会说出来).所以我想做一些R&D.虽然我喜欢Michael Borgwardt的解释,但只想展示一个解释,如果有人将来真的需要知道.

/**
 *
 * @author Perilbrain
 */
public class Appc {
    public Appc() {
        String x = "no name";
        x += "I have Added a name" + "We May need few more names" + Appc.this;
        x.concat(x);
        // x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
        //System.out.println(x);
    }

    public void Sb() {
        StringBuilder sbb = new StringBuilder("no name");
        sbb.append("I have Added a name");
        sbb.append("We May need few more names");
        sbb.append(Appc.this);
        sbb.append(sbb.toString());
        // System.out.println(sbb.toString());
    }
}
Run Code Online (Sandbox Code Playgroud)

以上课程的反汇编出来了

 .method public <init>()V //public Appc()
  .limit stack 2
  .limit locals 2
met001_begin:                                  ; DATA XREF: met001_slot000i
  .line 12
    aload_0 ; met001_slot000
    invokespecial java/lang/Object.<init>()V
  .line 13
    ldc "no name"
    astore_1 ; met001_slot001
  .line 14

met001_7:                                      ; DATA XREF: met001_slot001i
    new java/lang/StringBuilder //1st object of SB
    dup
    invokespecial java/lang/StringBuilder.<init>()V
    aload_1 ; met001_slot001
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    ldc "I have Added a nameWe May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    aload_0 ; met001_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    astore_1 ; met001_slot001
  .line 15
    aload_1 ; met001_slot001
    aload_1 ; met001_slot001
    invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
    pop
  .line 18
    return //no more SB created
met001_end:                                    ; DATA XREF: met001_slot000i ...

; ===========================================================================

;met001_slot000                                ; DATA XREF: <init>r ...
    .var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001                                ; DATA XREF: <init>+6w ...
    .var 1 is x Ljava/lang/String; from met001_7 to met001_end
  .end method
;44-1=44
; ---------------------------------------------------------------------------


; Segment type: Pure code
  .method public Sb()V //public void Sb
  .limit stack 3
  .limit locals 2
met002_begin:                                  ; DATA XREF: met002_slot000i
  .line 21
    new java/lang/StringBuilder
    dup
    ldc "no name"
    invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    astore_1 ; met002_slot001
  .line 22

met002_10:                                     ; DATA XREF: met002_slot001i
    aload_1 ; met002_slot001
    ldc "I have Added a name"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 23
    aload_1 ; met002_slot001
    ldc "We May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 24
    aload_1 ; met002_slot001
    aload_0 ; met002_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    pop
  .line 25
    aload_1 ; met002_slot001
    aload_1 ; met002_slot001
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 28
    return
met002_end:                                    ; DATA XREF: met002_slot000i ...


;met002_slot000                                ; DATA XREF: Sb+25r
    .var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001                                ; DATA XREF: Sb+9w ...
    .var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
  .end method
;96-49=48
; ---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

从上面的两个代码可以看出迈克尔是对的.在每种情况下,只创建一个SB对象.


Zof*_*ren 27

从Java 1.5开始,简单的一行连接"+"和StringBuilder.append()生成完全相同的字节码.

因此,为了代码可读性,请使用"+".

2例外:

  • 多线程环境:StringBuffer
  • 循环中的连接:StringBuilder/StringBuffer

  • 在 Java 1.5 之前,使用“+”和 StringBuffer.append() 进行简单的一行连接会生成完全相同的字节码(因为 StringBuilder 不存在)。从 Java 9 开始,使用“+”简单的一行连接生成的代码可能比 StringBuilder 更好。 (4认同)

rin*_*rer 22

使用最新版本的Java(1.8),disassembly(javap -c)显示了编译器引入的优化.+同样sb.append()会产生非常相似的代码.但是,如果我们+在for循环中使用它,那么检查行为是值得的.

在for循环中使用+添加字符串

Java的:

public String myCatPlus(String[] vals) {
    String result = "";
    for (String val : vals) {
        result = result + val;
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

ByteCode :( for循环摘录)

12: iload         5
14: iload         4
16: if_icmpge     51
19: aload_3
20: iload         5
22: aaload
23: astore        6
25: new           #3                  // class java/lang/StringBuilder
28: dup
29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload         6
38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc          5, 1
48: goto          12
Run Code Online (Sandbox Code Playgroud)

使用stringbuilder.append添加字符串

Java的:

public String myCatSb(String[] vals) {
    StringBuilder sb = new StringBuilder();
    for(String val : vals) {
        sb.append(val);
    }
    return sb.toString();
}
Run Code Online (Sandbox Code Playgroud)

ByteCdoe :( for循环摘录)

17: iload         5
19: iload         4
21: if_icmpge     43
24: aload_3
25: iload         5
27: aaload
28: astore        6
30: aload_2
31: aload         6
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc          5, 1
40: goto          17
43: aload_2
Run Code Online (Sandbox Code Playgroud)

但是有一些明显的区别.在第一种情况下,使用where +,StringBuilder为每个for循环迭代创建new ,并通过toString()调用(29到41)存储生成的结果.因此,您正在生成使用+operator in for循环时真正不需要的中间字符串.

  • @ChristopheRoussy 也许是许可证头。我怀疑“99%”是否是一个准确的测量数字。它更像是一个数字,说“如果你发现了一些不相关的差异,请不要对我挑剔” (2认同)

Zhe*_*lov 10

在Java 9中,版本1应该更快,因为它被转换为invokedynamic调用.更多细节可以在JEP-280中找到:

我们的想法是用对java.lang.invoke.StringConcatFactory的简单invokedynamic调用替换整个StringBuilder追加舞,它将接受需要连接的值.


Pao*_*sca 8

出于性能原因,不鼓励使用+=(String串联).之所以是:Java的String是不可变的,每一个新的拼接完成一个新的时间String创建(新建一个具有自已的旧的一个不同的指纹在字符串池).创建新字符串会给GC带来压力并减慢程序速度:对象创建很昂贵.

下面的代码应该使它更加实用和清晰.

public static void main(String[] args) 
{
    // warming up
    for(int i = 0; i < 100; i++)
        RandomStringUtils.randomAlphanumeric(1024);
    final StringBuilder appender = new StringBuilder();
    for(int i = 0; i < 100; i++)
        appender.append(RandomStringUtils.randomAlphanumeric(i));

    // testing
    for(int i = 1; i <= 10000; i*=10)
        test(i);
}

public static void test(final int howMany) 
{
    List<String> samples = new ArrayList<>(howMany);
    for(int i = 0; i < howMany; i++)
        samples.add(RandomStringUtils.randomAlphabetic(128));

    final StringBuilder builder = new StringBuilder();
    long start = System.nanoTime();
    for(String sample: samples)
        builder.append(sample);
    builder.toString();
    long elapsed = System.nanoTime() - start;
    System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);

    String accumulator = "";
    start = System.nanoTime();
    for(String sample: samples)
        accumulator += sample;
    elapsed = System.nanoTime() - start;
    System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);

    start = System.nanoTime();
    String newOne = null;
    for(String sample: samples)
        newOne = new String(sample);
    elapsed = System.nanoTime() - start;
    System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}
Run Code Online (Sandbox Code Playgroud)

运行结果报告如下.

builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us

builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us

builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us

builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us

builder - 10000 - elapsed: 3364us 
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us
Run Code Online (Sandbox Code Playgroud)

不考虑1次连接的结果(JIT尚未完成其工作),即使对于10次连接,性能损失也是相关的; 对于成千上万的连接,差异是巨大的.

从这个非常快速的实验中学到的经验(使用上面的代码很容易重现):永远不要使用+=连接字符串,即使在需要一些连接的非常基本的情况下(如上所述,创建新的字符串无论如何都是昂贵的并且对GC).


mat*_*t b 7

Apache Commons-Lang有一个ToStringBuilder类,非常容易使用.它既可以处理append-logic,也可以格式化toString的外观.

public void toString() {
     ToStringBuilder tsb =  new ToStringBuilder(this);
     tsb.append("a", a);
     tsb.append("b", b)
     return tsb.toString();
}
Run Code Online (Sandbox Code Playgroud)

将返回看起来像的输出com.blah.YourClass@abc1321f[a=whatever, b=foo].

或者使用链接以更精简的形式:

public void toString() {
     return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}
Run Code Online (Sandbox Code Playgroud)

或者,如果您想使用反射来包含该类的每个字段:

public String toString() {
    return ToStringBuilder.reflectionToString(this);
}
Run Code Online (Sandbox Code Playgroud)

如果需要,您还可以自定义ToString的样式.


小智 6

我认为我们应该采用 StringBuilder 附加方法。原因是:

  1. String 连接每次都会创建一个新的字符串对象(由于 String 是不可变对象),因此会创建 3 个对象。

  2. 使用字符串生成器,只会创建一个对象[StringBuilder 是可变的],并且将进一步的字符串附加到该对象。


Mar*_* An 6

请参阅以下示例:

static final int MAX_ITERATIONS = 50000;
static final int CALC_AVG_EVERY = 10000;

public static void main(String[] args) {
    printBytecodeVersion();
    printJavaVersion();
    case1();//str.concat
    case2();//+=
    case3();//StringBuilder
}

static void case1() {
    System.out.println("[str1.concat(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str = str.concat(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}

static void case2() {
    System.out.println("[str1+=str2]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str += UUID.randomUUID() + "---";
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}

static void case3() {
    System.out.println("[str1.append(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    StringBuilder str = new StringBuilder("");
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str.append(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");

}

static void saveTime(List<Long> executionTimes, long startTime) {
    executionTimes.add(System.currentTimeMillis() - startTime);
    if (executionTimes.size() % CALC_AVG_EVERY == 0) {
        out.println("average time for " + executionTimes.size() + " concatenations: "
                + NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(() -> 0))
                + " ms avg");
        executionTimes.clear();
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

java字节码版本:8
java.version:1.8.0_144
[str1.concat(str2)]
10000个并置的平均时间:0.096 ms
10000个并置的平均时间:0.185 ms平均
10000个并置的平均时间:0.327 ms平均并置的
时间10000个连接:10000个连接的
平均时间为0.501 ms 平均时间
创建的字符串长度:1950000 in 17745 ms
[str1 + = str2]
10000个连接的平均时间:0.21 ms平均
10000个连接的平均时间:0.652 ms
平均时间10000个串联:
平均1.129毫秒10000个串联的平均时间:1.727毫秒平均
10000个连接的平均时间:2.302 ms平均
创建的字符串长度:1950000 in 60279 ms
[str1.append(str2)]
10000个连接的平均时间:0.002 ms平均
10000个连接的平均时间:0.002 ms平均
10000个连接的平均时间: 0.002毫秒AVG
平均时间10000个级联:0.002毫秒AVG
平均时间10000个级联:0.002毫秒AVG
创建长度的字符串:1950000在100毫秒

随着字符串长度的增加,连接时间也会增加。
那是StringBuilder绝对需要的地方。
如您所见,串联:UUID.randomUUID()+"---"并没有真正影响时间。

PS:我不认为何时在Java中使用StringBuilder确实是重复的。
这个问题讨论的是toString(),大多数时候哪个时间不执行大型字符串的连接。


2019更新

由于java8时间,事情已经改变了一点。看来now(java13)的串联时间+=实际上与相同str.concat()。但是,StringBuilder串联时间仍然是恒定的。(上面的原始帖子经过了稍微的编辑,以添加更多详细的输出)

java字节码版本:13
java.version:13.0.1
[str1.concat(str2)]
10000个连接的平均时间:0.047 ms
10000个连接的平均时间:0.1 ms avg
10000个连接的平均时间:0.17 ms avg的
平均时间10000个连接:10000个连接的
平均时间为0.255 ms 平均时间
创建的字符串长度:1950000 in 9147 ms
[str1 + = str2]
10000个连接的平均时间:0.037 ms平均
10000个连接的平均时间:0.097 ms
平均时间10000次串联:
平均0.249毫秒10000次串联的平均时间:0.298毫秒平均
10000个连接的平均时间:0.326 ms平均
创建的字符串长度:1950000 in 10191 ms
[str1.append(str2)]
10000个连接的平均时间:0.001 ms平均
10000个连接的平均时间:0.001 ms平均
10000个连接的平均时间: 0.001毫秒AVG
平均时间10000个级联:0.001毫秒AVG
平均时间10000个级联:0.001毫秒AVG
创建长度的字符串:1950000在43毫秒

值得注意的是,bytecode:8/java.version:13与之相比,组合具有良好的性能优势bytecode:8/java.version:8