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

10,736

Solution 1

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();

Solution 2

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);
  }
}

Solution 3

See the implementation of each and compare them:

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;
}

Which one do you prefer when you have the option to use both?

If I have 1000s of lines, I will really prefer to use append(char c) for better performances, but for one line, it doesn't really matter.

Share:
10,736
Zeeshan
Author by

Zeeshan

Updated on June 04, 2022

Comments

  • Zeeshan
    Zeeshan about 2 years

    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();
    
  • icza
    icza over 9 years
    sb.append("a") doesn't create new strings. String literals are interened.
  • Keval
    Keval over 9 years
    but what if there is no string object with value 'a' ? will that create a new object in string pool ?
  • icza
    icza over 9 years
    Yes, if it is not yet in the pool, it will be added to it. But since the String literal is part of the class definition, it might be added to the String pool when the class is loaded (implementation dependent).
  • avalancha
    avalancha over 6 years
    As usual with these questions it goes: yeah, maybe, could be a performance gain. Make a benchmark on your system, get some proof, then decide how much effort it is worth putting in for your situation
  • pramodc84
    pramodc84 over 5 years
    Above one will violate another PMD rule pmd.sourceforge.io/pmd-4.3/rules/strings.html. AvoidStringBufferField says StringBuffers can grow quite a lot, and so may become a source of memory leak (if the owning class has a long life time).