由于大量字符串,我们在应用程序中遇到了非常糟糕的性能损失:创建用于记录应用程序状态。 我们计划至少为记录器移动到String构建器。 我有一个困惑是:
因为字符串构建器是这样调用的:
StringBuilder sb = new StringBuilder();
sb.append("Very big String object....");
如果我是对的,"非常大的String对象...."仍然是一个构造函数,在内存中创建非常大的String对象(不可变),它保留在String池中。
那么使用String构建器比在这种情况下使用吗?因为它肯定会再创建一个对象,这将是垃圾收集。
但是使用String构造函数创建的String(double qoutes)"非常大的String对象....",仍然在内存池中。
答案 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
,因为您正在构造一个静态的,不可变的字符串对象。