在 StringBuffer append 中对单字符值使用字符而不是 String

Using character instead of String for single-character values in StringBuffer append

我正在通过 PMD 规则 AppendCharacterWithChar。它说 Avoid concatenating characters as strings in StringBuffer.append.

StringBuffer sb = new StringBuffer();
  // Avoid this
  sb.append("a");

  // use instead something like this
  StringBuffer sb = new StringBuffer();
  sb.append('a');

我真的需要这个 PMD 规则吗?下面两段代码性能差异大吗?

String text = new StringBuffer().append("some string").append('c').toString();

String text = new StringBuffer().append("some string").append("c").toString();

将字符附加为 char 总是比将其附加为 String 更快。

但是性能差异重要吗?如果你只做一次,它不会。如果它在一个循环中重复其 body 一百万次,那么是的,它可能很重要。

如果您在编译时已经有了该字符,只需将其附加为一个字符即可。如果它存储在 String 类型的变量中,请不要访问它,例如使用 String.charAt(0) 或其他一些方式,只需附加 String.

旁注:

喜欢StringBuilder class to StringBufferStringBuilder 更快,因为它的方法不是同步的(在大多数情况下你不需要同步)。

旁注 #2:

这不会编译:

String text = new StringBuffer().append("some string").append('c');

append() returns StringBuffer 用于链接。你需要在上面调用 toString():

String text = new StringBuffer().append("some string").append('c').toString();

查看每个的实现并进行比较:

public AbstractStringBuilder append(char c):

public AbstractStringBuilder append(char c) {
    int newCount = count + 1;
    if (newCount > value.length)
        expandCapacity(newCount);
    value[count++] = c;
    return this;
}

public AbstractStringBuilder append(String str):

public AbstractStringBuilder append(String str) {
    if (str == null) str = "null";
    int len = str.length();
    if (len == 0) return this;
    int newCount = count + len;
    if (newCount > value.length)
        expandCapacity(newCount);
    str.getChars(0, len, value, count);
    count = newCount;
    return this;
}

当您可以选择同时使用两者时,您更喜欢哪一个?

如果我有 1000 行,我真的更喜欢使用 append(char c) 以获得更好的性能,但对于一行,这并不重要。

是的,它是正确的 避免在 StringBuffer.append 中将字符连接为字符串,因为每当您编写 sb.append("a") 时,都意味着您正在创建一个值为 [=11 的字符串对象=] 和 new String 意味着 Stringpool 中的新 String 对象和新 String 对象,这意味着不必要地 space 容纳在堆 space.

出于好奇,我 运行 使用 jmh(包括 GC 监控)进行了微基准测试。使用 String 稍微慢一点,但差异很小:每次调用大约 5 ns(纳秒)并且在 GC activity.

上没有显着差异

如果您调用 append("c") 而不是 append('c') 一百万次,您的程序会增加 5 毫秒。

基准测试结果,包括 gc 时间 - n 表示 StringBuilder 的初始长度:

Benchmark                             (n)  Mode  Cnt     Score     Error   Units
SO28344.appendChar                      0  avgt   30    16.476 ±   0.331   ns/op
SO28343294.appendChar:·gc.time          0  avgt   30   256.000                ms
SO28343294.appendString                 0  avgt   30    22.048 ±   0.345   ns/op
SO28343294.appendString:·gc.time        0  avgt   30   220.000                ms

SO28343294.appendChar                  50  avgt   30    17.323 ±   0.967   ns/op
SO28343294.appendChar:·gc.time         50  avgt   30    67.000                ms
SO28343294.appendString                50  avgt   30    20.944 ±   1.466   ns/op
SO28343294.appendString:·gc.time       50  avgt   30    74.000                ms

SO28343294.appendChar                1000  avgt   30    58.396 ±   0.811   ns/op
SO28343294.appendChar:·gc.time       1000  avgt   30    25.000                ms
SO28343294.appendString              1000  avgt   30    64.572 ±   4.779   ns/op
SO28343294.appendString:·gc.time     1000  avgt   30    24.000                ms

代码:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class SO28343294 {

  @Param({"0", "50", "1000"}) int n;
  Random r = new Random();
  StringBuilder sb;
  String s;
  char c;

  @Setup(Level.Invocation) public void populate() {
    sb = new StringBuilder(n + 5);
    for (int i = 0; i < n; i++) {
      sb.append((char) (r.nextInt(26) + 'a'));
    }
    c = (char) (r.nextInt(26) + 'a');
    s = new String(new char[] { c });
  }

  @Benchmark public StringBuilder appendString() {
    return sb.append(s);
  }

  @Benchmark public StringBuilder appendChar() {
    return sb.append(c);
  }
}