当在Java中使用StringBuilder时仍然创建String时,String构建器如何创建可变对象?

时间:2016-01-11 11:15:03

标签: java performance stringbuilder

由于大量字符串,我们在应用程序中遇到了非常糟糕的性能损失:创建用于记录应用程序状态。 我们计划至少为记录器移动到String构建器。 我有一个困惑是:

因为字符串构建器是这样调用的:

StringBuilder sb = new StringBuilder();
sb.append("Very big String object....");

如果我是对的,"非常大的String对象...."仍然是一个构造函数,在内存中创建非常大的String对象(不可变),它保留在String池中。

那么使用String构建器比在这种情况下使用吗?因为它肯定会再创建一个对象,这将是垃圾收集。

但是使用String构造函数创建的String(double qoutes)"非常大的String对象....",仍然在内存池中。

3 个答案:

答案 0 :(得分:2)

你不对。 "Very big String object...."是一个编译时常量,而不是构造函数,表示它的String对象将进入常量池,以便在每次需要时重用。将创建的对象是实际构建器,其后备字符数组以及添加日志信息后生成的String - 所有这些都将随每个输出语句而变化。

如果您只使用固定输出字符串,那么使用StringBuilder就没有意义,但构建器的要点是变化的部分。

所有这一切,如果日志记录是一个真正的性能问题,String创建不是真正的瓶颈(并且日志消息的实际I / O是),或者您正在构建日志消息即使它们没有输出(例如,对于DEBUG级别的日志语句)。在后一种情况下,如果禁用记录器,则应该完全避免完全构建它们;现代日志框架(如slf4j)会自动执行此操作(如果log.debug()日志已关闭,则slf4j中的DEBUG会立即退出。

答案 1 :(得分:2)

StringBuilder可以提高多次添加的内存消耗和性能。让我们分析下一个例子(假设javac没有优化任何String个连接):

String s = "a" + "b" + "c" + "d" + ... + "z"; 
StringBuilder sb = new StringBuilder("a").append("b").append("c")....append("z");

如果String+连接,则java会从左到右添加字符串,每次创建一个新字符串:"ab",然后是"abc",然后是{{ 1}},因此25个新字符串,每次它将完全复制前一个结果。虽然"abcd"只是将每个字符串添加到其自己的StringBuilder数组中,但不会创建任何冗余对象。

现在让char[]为字符串数,n - 每个字符串的长度。在这种情况下,l m的复杂性将为+,因为每次复制前一个字符串连接时。因此,我们可以得出结论,O(l*m)案例的汇总时间(和内存(!))复杂度将为O(l*n*n)。在String的情况下,它将是StringBuilder

关于日志记录 - 小型性能比较:

O(l*n)

结果:

@Benchmark
public void stringConcatenation(Blackhole bh) {
    // By using all these string we should prevent string builder optimizations.
    String a = "start ";
    String b = a + "first";
    String c = b + " inside ";
    String d = c + "second";
    String e = d + ", ";
    String f = e + 1024;
    bh.consume(a);
    bh.consume(b);
    bh.consume(c);
    bh.consume(d);
    bh.consume(e);
    bh.consume(f);
}

@Benchmark
public void stringBuilder(Blackhole bh) {
    StringBuilder sb = new StringBuilder("start ")
            .append("first")
            .append(" inside ")
            .append("second")
            .append(", ")
            .append(1024);
    bh.consume(sb.toString());
}

@Benchmark
public void logback(Blackhole bh) {
    // Logback formatting
    bh.consume(MessageFormatter.arrayFormat("start {} inside {}, {}", new Object[]{"first", "second", 1024}).getMessage());
}

@Benchmark
public void log4j(Blackhole bh) {
    // Log4j formatting
    bh.consume(messageFactory.newMessage("start {} inside {}, {}", "first", "second", 1024));
}

正如你可以看到一些人所建议的那样#34;而是使用日志框架格式化器#34;如果你真的记录很多,可能不是更好的选择。

答案 2 :(得分:1)

当您想通过连接值构建字符串时,

StringBuilder类很有用,并且在代码的不同句子中添加了新值:

StringBuilder sb = new StringBuilder("Counting from 0 to 9: ");
for(int i = 0 ; i < 10 ; i++) {
    sb.append(i).append(' ');
}
System.out.println(sb.toString());

使用此类是因为,在此示例中,java字符串连接运算符+将提供较差的性能。

在您的示例中,您不需要StringBuilder,因为您正在构造一个静态的,不可变的字符串对象。