Java String Interpolation Performance Study
2021-04-10tl;dr In Java,
String.replaceshould be used for string interpolation to reduce performance impact, and concatenating string with+is performant as well.
Generally, to perform string interpolation, one would provide a templated string, e.g. "[{1}] message received with id: {2} with size: {3}". In Java, the closest you get out of the box is String.format for this.
However, there is a common belief that String.format is slow and should not be used in performance critical applications. Some suggests to use String.replace instead in such situation.
Benchmarking
To test which method is better, I wrote code to interpolate string for integer, string and double in both ways and do benchmark with JMH on a Mac with 1.6 GHz Intel Core i5 with Zulu OpenJDK 64-Bit Server VM (build 11.0.10+9-LTS, mixed mode) based on throughput.
Interpolation with replace
@Benchmark
public void testStringReplace(){
System.out.println(
"{1} {2} {3}"
.replace("{1}", String.valueOf(11))
.replace("{2}", "Eleven")
.replace("{3}", String.valueOf(1.1))
);
}
Interpolation with format
@Benchmark
public void testStringFormat(){
System.out.println(String.format("%d %s %f", 11, "Eleven", 1.1));
}
Result
Benchmark Mode Cnt Score Error Units
StringInterpolationJavaBenchmark.testStringFormat thrpt 2 25191.009 ops/s
StringInterpolationJavaBenchmark.testStringReplace thrpt 2 46889.833 ops/s
As the result suggest, using replace is much faster than format
Why
Comparing their implementation tells most of the story. The following analysis is based on source code of OpenJDK 11
Implementation of String.replace
public String replace(CharSequence target, CharSequence replacement) {
String tgtStr = target.toString();
String replStr = replacement.toString();
int j = indexOf(tgtStr);
if (j < 0) {
return this;
}
int tgtLen = tgtStr.length();
int tgtLen1 = Math.max(tgtLen, 1);
int thisLen = length();
int newLenHint = thisLen - tgtLen + replStr.length();
if (newLenHint < 0) {
throw new OutOfMemoryError();
}
StringBuilder sb = new StringBuilder(newLenHint);
int i = 0;
do {
sb.append(this, i, j).append(replStr);
i = j + tgtLen;
} while (j < thisLen && (j = indexOf(tgtStr, j + tgtLen1)) > 0);
return sb.append(this, i, thisLen).toString();
}
It loop over the original string, append the either the original section of string, or the replaced string to a new string builder, and return the newly built string from the builder.
Implementation of String.format
public static String format(String format, Object... args) {
return new Formatter().format(format, args).toString();
}
Every time it will create a new Formatter.
private List<FormatString> parse(String s) {
ArrayList<FormatString> al = new ArrayList<>();
Matcher m = fsPattern.matcher(s);
// other code...
The Formatter requires to parse the format with RegEx on each invocation. Here, RegEx parsing is expensive in terms of performance.
Thus, String.replace is much performant than String.format.
Can we go further?
replaceFirst
Some may think replaceFirst can reduce iterations by early exit in finding string.
@Benchmark
public void testStringReplaceFirst(){
// NOTE: it is regex "\\{}", not simple curly bracket "{}"
System.out.println(
"{} {} {}"
.replaceFirst("\\{}", String.valueOf(11))
.replaceFirst("\\{}", "Eleven")
.replaceFirst("\\{}", String.valueOf(1.1))
);
}
Benchmark Mode Cnt Score Error Units
StringInterpolationJavaBenchmark.testStringReplaceFirst thrpt 2 30718.865 ops/s
Here, the performance is worsen, because replaceFirst relies on RegEx.
Concatenate strings with +
@Benchmark
public void testStringConcat(){
System.out.println(
String.valueOf(11) + " " + "Eleven" + " " + String.valueOf(1.1)
);
}
Benchmark Mode Cnt Score Error Units
StringInterpolationJavaBenchmark.testStringConcat thrpt 2 47188.903 ops/s
The performance is better than replace here, but it doesn't look like string interpolation and the code is not as elegant as the the replace version.
Closing
If your Java application is performance critical, String.replace and concatenate strings with + are more performant than String.format.
All above benchmark code is available in this repo.
If you like this article, remember to show your support by buy me a book.