JUAN CALVOPINA M
JUAN CALVOPINA M

Reputation: 3984

Performance between String.format and StringBuilder

To concatenate String we often use StringBuilder instead of String + String, but also we can do the same with String.format which returns the formatted string by given locale, format and arguments.

Examples:

Concatenate the string with StringBuilder

String concatenateStringWithStringBuilder(String name, String lName, String nick) {
    final StringBuilder sb = new StringBuilder("Contact {");
    sb.append(", name='").append(name)
      .append(", lastName='").append(lName)
      .append(", nickName='").append(nick)
      .append('}');
    return sb.toString();
}

Concatenate the string with StringFormat:

String concatenateStringWithStringFormat(String name, String lName, String nick) {
    return String.format("Contact {name=%s, lastName=%s, nickName=%s}", name, lName, nick);
}

In performance, is String.Format as efficient as StringBuilder? Which one is better to concatenate strings and why?

UPDATE

I checked the similar question, but doesn´t answer my question. So far I have used StringBuilder to concatenate strings, should I follow it using? Or should I use String.format? the question is which one is better and why?

Upvotes: 51

Views: 67852

Answers (4)

John
John

Reputation: 6688

I just replaced my String.Format with StringBuilder after profiling, I saved 10% time in a function that was spending 60% of its time in String.Format.

I hoped for more performance, and stumbled on this post as I questioned why. Svetlin makes some good points:

For instance String Builder ... code will be much more unreadable, and and it would be easier to make a mistake.

and

What is "better" solely depends on your requirements

My requirements are for a terse filename, a few characters here, a loop index there. Allow me to present my test code and findings:

import java.util.Collections;
import java.util.List;

class Scratch {
    public interface Worker {
        void runIts(StringBuilder sb, int iIterations, String seq, int j);
    }

    public static void main(String[] args) throws Exception {
        String seq = "scjwodcAKFQBWDCM219247148asidifbaMZNCFSADF28123";
        final int iIterations = 1000;
        final int jIterations = 10000; // 0_000;
        StringBuilder sb = new StringBuilder();
        Worker[] methods = new Worker[] {
                Scratch::doSomeSB,
                Scratch::doSomeFormat,
                Scratch::doSomeConcat,
        };
        List<Integer> indices = new java.util.ArrayList<>(List.of(0, 1, 2));

        float[] methTimes = new float[3];
        for(int j = 0; j < jIterations; j++) {
            Collections.shuffle(indices);
            for(int k = 0; k < 3; k++) {
                long start = System.nanoTime();
                methods[indices.get(k)].runIts(sb, iIterations, seq, j);
                long end = System.nanoTime();
                methTimes[indices.get(k)] += end - start;
                methTimes[indices.get(k)] += Math.round((end - start) / 1000000f);
            }
        }
        System.out.println("Concatenation = " + Math.round(methTimes[2] / 1000000f) + "ms") ;
        System.out.println("Format = " + Math.round(methTimes[1] / 1000000f)  + "ms") ;
        System.out.println("StringBuilder = " + Math.round(methTimes[0] / 1000000f)  + "ms") ;
    }

    private static void doSomeSB(StringBuilder sb, int iIterations, String seq, int j) {
        sb.append("j ");
        sb.append(j);
        int eoj = sb.length();
        for (int i = 0; i < iIterations; i++) {
            sb.setLength(eoj);
            final char c = seq.charAt(i % seq.length());
            sb.append("; i ").append(i).append("; 2i ").append(i * 2).append("; see: ").append(c).append("; f is ").append(seq.indexOf(c));
            String s = sb.toString();
        }
    }

    private static void doSomeFormat(StringBuilder sb, int iIterations, String seq, int j) {
        for (int i = 0; i < iIterations; i++) {
            final char c = seq.charAt(i % seq.length());
            String s = String.format("j %d; i %s; 2i %s; see: %c; f is %d", j, i, i * 2, c, seq.indexOf(c));
        }
    }

    private static void doSomeConcat(StringBuilder sb, int iIterations, String seq, int j) {
        for (int i = 0; i < iIterations; i++) {
            final char c = seq.charAt(i % seq.length());
            String s = "j " + j + "; i " + i + "; 2i " + i * 2 + "; see: " + c + "; f is " + seq.indexOf(c);
        }
    }
}

There are more iterations than I need, but the formatting is almost as complex as I need.

I was surprised by the result for StringBuilder.

Concatenation = 45ms

Format = 10543ms

StringBuilder = 212485ms

Over 3 minutes!

I had to check the horrible StringBuilder code again, with shorter iterations. So many times I tabulated it:

iterations StringBuilder Format Concat
10 5 79 21
50 25 143 41
100 97 198 62
500 694 511 118
1000 2357 861 151
5000 51051 3465 346
10000 212485 10543 45

So StringBuilder is incredibly performant, as expected, to a point. It doesn't scale well. I'd speculate it is allocating a lot, and the GC is catching up with it at scale.

At the risk of over-emphasising this inefficiency, I drew this graph:

The effect of iterations on the efficiency of StringBuilder

Upvotes: 0

JUAN CALVOPINA M
JUAN CALVOPINA M

Reputation: 3984

After doing a little test with StringBuilder vs String.format I understood how much time it takes each of them to solve the concatenation. Here the snippet code and the results

Code:

String name = "stackover";
String lName = " flow";
String nick = " stackoverflow";
String email = "[email protected]";
int phone = 123123123;

//for (int i = 0; i < 10; i++) {
long initialTime1 = System.currentTimeMillis();
String response = String.format(" - Contact {name=%s, lastName=%s, nickName=%s, email=%s, phone=%d}",
                                name, lName, nick, email, phone);
long finalTime1 = System.currentTimeMillis();
long totalTime1 = finalTime1 - initialTime1;
System.out.println(totalTime1 + response);

long initialTime2 = System.currentTimeMillis();
final StringBuilder sb = new StringBuilder(" - Contact {");
sb.append("name=").append(name)
  .append(", lastName=").append(lName)
  .append(", nickName=").append(nick)
  .append(", email=").append(email)
  .append(", phone=").append(phone)
  .append('}');
String response2 = sb.toString();
long finalTime2 = System.currentTimeMillis();
long totalTime2 = finalTime2 - initialTime2;
System.out.println(totalTime2 + response2);
//}

After of run the code several times, I saw that String.format takes more time:

String.format: 46: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}
String.format: 38: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}
String.format: 51: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}

But if I run the same code inside a loop, the result change.

String.format: 43: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}
String.format: 1: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}
String.format: 1: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, [email protected], phone=123123123}

The first time String.format runs it takes more time, after of that the time is shorter even though it does not become constant as a result of StringBuilder

As @G.Fiedler said: "String.format has to parse the format string..."

With these results it can be said that StringBuilder is more efficient thanString.format

Upvotes: 22

Svetlin Zarev
Svetlin Zarev

Reputation: 15713

What is "better" solely depends on your requirements:

  • For instance String Builder will be faster, but the code will be much more unreadable, and and it would be easier to make a mistake.

  • On the other hand String.format() produces more readable code at the cost of performance.

JMH benchmark to illustrate the performance difference (notice that the string builder code is longer and very hard to understand how the resulting string would look like):

@Fork(1)
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Measurement(iterations = 10)
@Warmup(iterations = 10)
@BenchmarkMode(Mode.Throughput)
public class StringFormatBenchmark {
    private String name = "UserName";
    private String lName = "LUserName";
    private String nick = "UserNick";

    @Benchmark
    public void stringFormat(Blackhole blackhole) {
        final String result = String.format("Contact {name=%s, lastName=%s, nickName=%s}", name, lName, nick);
        blackhole.consume(result);
    }

    @Benchmark
    public void stringBuilder(Blackhole blackhole) {
        final StringBuffer sb = new StringBuffer("Contact {");
        sb.append(", name='").append(name)
                .append(", lastName='").append(lName)
                .append(", nickName='").append(nick)
                .append('}');
        final String result = sb.toString();
        blackhole.consume(result);
    }
}

And the results:

Benchmark                             Mode  Cnt      Score     Error   Units
StringFormatBenchmark.stringBuilder  thrpt   10  10617.210 ± 157.302  ops/ms
StringFormatBenchmark.stringFormat   thrpt   10    960.658 ±   7.398  ops/ms

For non performance critical code I prefer using the String.format(), because it's easier and more pleasant to use. Also it's visible what the resulting string would look like, by simply looking at the pattern. If I'm doing a performance critical code, or something that has to have a low GC impact, I would use a StringBuilder because it's faster and can be reused.

Upvotes: 39

G. Fiedler
G. Fiedler

Reputation: 726

StringBuilder is faster, because String.format has to parse the format string (a complex domain specific language). And that's expensive.

StringBuilder instead of String + String

BTW: It's the same, because it results in the same byte code (since Java 1.5).

Upvotes: 13

Related Questions