【问题标题】:How does String builder create mutable object , when String is still created when using StringBuilder in Java?在Java中使用StringBuilder时仍然创建String时,String builder如何创建可变对象?
【发布时间】:2016-01-11 11:15:03
【问题描述】:

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

因为字符串生成器是这样调用的:

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

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

那么在这种情况下使用 String builder 有什么用呢?因为它肯定会创建一个更多的对象,尽管会被垃圾回收。

但是使用 String 构造函数 (double qoutes) "Very big String object...." 创建的 String 仍在内存池中。

【问题讨论】:

  • 1.应使用参数化日志记录来避免创建运行时不需要的此类对象实例。 2. 在您的应用程序中使用动态日志级别更改,以有效利用 DEBUG/INFO 级别日志记录的优势。

标签: java performance stringbuilder


【解决方案1】:

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

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

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

【讨论】:

  • 即使 DEBUG 级别设置为 ERROR: in log.debug( "Very long string....") ,“非常长的字符串....”构造函数仍然会被执行并且会消耗内存。
  • @user3769778 你错了。该常量字符串只有一个副本。使用多少次无关紧要。
【解决方案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",然后"abcd",因此有25个新字符串,以及每次它都会完全复制以前的结果。而StringBuilder 将简单地将每个字符串添加到其自己的char[] 数组中,而不会创建任何冗余对象。

现在让n 是字符串的数量,l - 每个字符串的长度。在这种情况下,mth + 的复杂度将是 O(l*m),因为每次都会复制整个先前的字符串连接。因此,我们可以得出结论,对于String 情况,汇总时间(和内存(!))复杂度将为O(l*n*n)。而在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));
}

结果:

Benchmark                          Mode  Cnt         Score         Error  Units
LogBenchmark.stringConcatenation  thrpt    5   9080147,269 ?  988134,269  ops/s
LogBenchmark.stringBuilder        thrpt    5  27136050,849 ? 2776464,863  ops/s
LogBenchmark.logback              thrpt    5   3579746,331 ?  346554,072  ops/s
LogBenchmark.log4j                thrpt    5   4992342,169 ?  335971,537  ops/s

因此,正如您所看到的,如果您实际记录大量日志,那么“改用日志框架格式化程序”可能不是更好的选择。

【讨论】:

  • 如果也能看到 log4j 2 那就太好了:)
  • 谢谢,有趣的结果
【解决方案3】:

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,因为您正在构造一个静态、不可变的字符串对象。

【讨论】:

  • 在大多数情况下,使用 java 8 StringJoiner 会是更好的选择,我也在某处读过,我不记得在 java 8 之后,甚至 '+' 操作在后台由 JVM 转换为 StringBuilder
  • @HRgiger AFAIK,当所有块都在同一个操作中时,“+”运算符将转换为 StringBuilder。例如:String newString = "The value is " + value + ". Goodbye";
  • 可能我需要为此做一些阅读:)(我不是反对者)
猜你喜欢
  • 2011-11-28
  • 1970-01-01
  • 1970-01-01
  • 2011-06-06
  • 2014-11-26
  • 1970-01-01
  • 2011-07-31
  • 2015-06-15
  • 2023-04-03
相关资源
最近更新 更多