给定下面的2个toString()实现,哪个是首选的:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}

or

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

?

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


当前回答

使用'+'的字符串连接的性能更昂贵,因为它必须创建一个全新的字符串副本,因为字符串在java中是不可变的。如果连接非常频繁,例如:在循环中,这将发挥特别的作用。 以下是我的想法,当我试图做这样的事情:

一般规则:

在单个字符串赋值中,使用字符串连接是很好的。 如果您正在循环构建一个大的字符数据块,请使用StringBuffer。 在String上使用+=总是比使用StringBuffer效率低,所以它应该敲响警钟——但在某些情况下,所获得的优化与可读性问题相比微不足道,所以使用你的常识。

这里有一篇关于这个话题的Jon Skeet博客。

其他回答

使用'+'的字符串连接的性能更昂贵,因为它必须创建一个全新的字符串副本,因为字符串在java中是不可变的。如果连接非常频繁,例如:在循环中,这将发挥特别的作用。 以下是我的想法,当我试图做这样的事情:

一般规则:

在单个字符串赋值中,使用字符串连接是很好的。 如果您正在循环构建一个大的字符数据块,请使用StringBuffer。 在String上使用+=总是比使用StringBuffer效率低,所以它应该敲响警钟——但在某些情况下,所获得的优化与可读性问题相比微不足道,所以使用你的常识。

这里有一篇关于这个话题的Jon Skeet博客。

值得一提的是,正如@ZhekaKozlov指出的那样,

自Java 9以来,+更快,除非JVM不知道如何优化它(例如,循环中的连接)。

我检查了以下代码的字节码(在Java 17中):

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();
    }
}

For toStringPlus:

 0: aload_1
 1: invokedynamic #7,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
 6: areturn

for toStringBuilder:

 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和并传入方法参数{a:\u0001, b:, c:} (\u0001是参数占位符)。 而StringBuilder版本必须以“诚实”的方式来实现。 我想我们可以看到为什么+现在更快了。

这取决于字符串的大小。

请看下面的例子:

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();
    }
}

输出:

java bytecode version:8 java.version: 1.8.0_144 [str1.concat(str2)] average time for 10000 concatenations: 0.096 ms avg average time for 10000 concatenations: 0.185 ms avg average time for 10000 concatenations: 0.327 ms avg average time for 10000 concatenations: 0.501 ms avg average time for 10000 concatenations: 0.656 ms avg Created string of length:1950000 in 17745 ms [str1+=str2] average time for 10000 concatenations: 0.21 ms avg average time for 10000 concatenations: 0.652 ms avg average time for 10000 concatenations: 1.129 ms avg average time for 10000 concatenations: 1.727 ms avg average time for 10000 concatenations: 2.302 ms avg Created string of length:1950000 in 60279 ms [str1.append(str2)] average time for 10000 concatenations: 0.002 ms avg average time for 10000 concatenations: 0.002 ms avg average time for 10000 concatenations: 0.002 ms avg average time for 10000 concatenations: 0.002 ms avg average time for 10000 concatenations: 0.002 ms avg Created string of length:1950000 in 100 ms

随着字符串长度的增加,+=和.concat的连接时间也会增加,后者效率更高,但仍然是非常量 这就是绝对需要StringBuilder的地方。

附注:我不认为什么时候在Java中使用StringBuilder是一个真正的复制。 这个问题讨论的是toString(),它在大多数情况下不会执行大字符串的连接。


2019年更新

自java8时代以来,情况发生了一些变化。现在看来(java13), +=的连接时间实际上与str.concat()相同。但是StringBuilder的连接时间仍然是固定的。(上面的原始帖子略有编辑,添加了更多详细的输出)

java bytecode version:13 java.version: 13.0.1 [str1.concat(str2)] average time for 10000 concatenations: 0.047 ms avg average time for 10000 concatenations: 0.1 ms avg average time for 10000 concatenations: 0.17 ms avg average time for 10000 concatenations: 0.255 ms avg average time for 10000 concatenations: 0.336 ms avg Created string of length:1950000 in 9147 ms [str1+=str2] average time for 10000 concatenations: 0.037 ms avg average time for 10000 concatenations: 0.097 ms avg average time for 10000 concatenations: 0.249 ms avg average time for 10000 concatenations: 0.298 ms avg average time for 10000 concatenations: 0.326 ms avg Created string of length:1950000 in 10191 ms [str1.append(str2)] average time for 10000 concatenations: 0.001 ms avg average time for 10000 concatenations: 0.001 ms avg average time for 10000 concatenations: 0.001 ms avg average time for 10000 concatenations: 0.001 ms avg average time for 10000 concatenations: 0.001 ms avg Created string of length:1950000 in 43 ms

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

对于当前的编译器是否仍然需要使用StringBuilder,似乎存在一些争论。所以我想谈谈我的经验。

我有一个包含10k条记录的JDBC结果集(是的,我需要将它们全部放在一个批处理中)。在我的Java 1.8机器上使用+运算符大约需要5分钟。对于相同的查询,使用stringBuilder.append("")只需不到一秒。

所以差异是巨大的。在循环中,StringBuilder要快得多。

我也和我的老板在使用append还是+的问题上发生了冲突。因为他们正在使用追加(我仍然不能弄清楚,因为他们说每次创建一个新对象)。 所以我想做些研究。虽然我喜欢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());
    }
}

而上述类的拆卸出来为

 .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
; ---------------------------------------------------------------------------

从上面的两个代码你可以看出Michael是对的。每种情况下只创建一个SB对象。