Reputation: 3984
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
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:
Upvotes: 0
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
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
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