Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I was going through the PMD rule AppendCharacterWithChar. It says 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');

Do I really need this PMD rule? Is there much performance difference between the following two piece of code?

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

String text = new StringBuffer().append("some string").append("c").toString();
like image 227
Zeeshan Avatar asked Feb 05 '15 11:02

Zeeshan


2 Answers

Appending a character as a char will always be faster than appending it as a String.

But does the performance difference matter? If you just do it once, it doesn't. If it is inside a cycle repeating its body a million times, then yes, it might matter.

If you already have the character at compile time, just append it as a character. If it is stored in a variable with String type, don't bother accessing it e.g. with String.charAt(0) or some other ways, simply just append the String.

On a Side Note:

Favor the StringBuilder class to StringBuffer. StringBuilder is faster because its methods are not synchronized (which you don't need in most cases).

On a Side Note #2:

This won't compile:

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

append() returns StringBuffer for chaining. You need to call toString() on it:

String text = new StringBuffer().append("some string").append('c').toString();
like image 103
icza Avatar answered Sep 18 '22 12:09

icza


Out of curiosity I ran a micro benchmark with jmh (including GC monitoring). Using a String is marginally slower but the difference is minimal: around 5 ns (nanoseconds) per invocation and no significant difference on GC activity.

If you called append("c") instead of append('c') one million times, it would add 5 ms to your program.

Benchmark results, including gc time - n represents the initial length of the 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

Code:

@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);
  }
}
like image 26
assylias Avatar answered Sep 21 '22 12:09

assylias