给定下面的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?


当前回答

版本1更可取,因为它更短,而且编译器实际上会把它转换成版本2——没有任何性能差异。

更重要的是,我们只有3个 属性,它可能不会生成 不同,但在什么情况下 从concat转到builder?

当你在一个循环中连接时——那通常是编译器不能自己替换StringBuilder的时候。

其他回答

这是我在Java8中检查的

Using String concatenation Using StringBuilder long time1 = System.currentTimeMillis(); usingStringConcatenation(100000); System.out.println("usingStringConcatenation " + (System.currentTimeMillis() - time1) + " ms"); time1 = System.currentTimeMillis(); usingStringBuilder(100000); System.out.println("usingStringBuilder " + (System.currentTimeMillis() - time1) + " ms"); private static void usingStringBuilder(int n) { StringBuilder str = new StringBuilder(); for(int i=0;i<n;i++) str.append("myBigString"); } private static void usingStringConcatenation(int n) { String str = ""; for(int i=0;i<n;i++) str+="myBigString"; }

如果对大量的字符串使用字符串连接,这真的是一场噩梦。

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms

出于性能考虑,不鼓励使用+=(字符串连接)。原因是:Java String是一个不可变的,每当一个新的连接完成时,一个新的String就会被创建(新的String与已经在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);
}

下面报告了运行的结果。

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

不考虑1个连接的结果(JIT还没有完成它的工作),即使对于10个连接,性能惩罚也是相关的;对于成千上万的连接,差异是巨大的。

从这个非常快速的实验中得到的教训(很容易用上面的代码重现):永远不要使用+=将字符串连接在一起,即使是在需要一些连接的非常基本的情况下(正如前面所说,创建新字符串无论如何都是昂贵的,并且会给GC带来压力)。

这取决于字符串的大小。

请看下面的例子:

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组合具有良好的性能优势

我认为这张图对于比较所有使用string的类非常有用:

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

一般规则:

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

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