I put together a small benchmark to test this out because I was curious. It initializes the List
with size
randomly-generated lowercase String
s, each having a length of 10
:
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Fork(3)
public class MyBenchmark {
@Param({"10", "100", "1000", "10000", "100000"})
private int size;
private List<String> finalWords;
@Setup(Level.Invocation)
public void initialize() {
finalWords = IntStream.range(0, size)
.mapToObj(i -> {
return ThreadLocalRandom.current()
.ints(10, 'a', 'z' + 1)
.mapToObj(c -> Character.toString((char) c))
.collect(Collectors.joining());
}).collect(Collectors.toList());
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
@Benchmark
public String stringBuilder() {
StringBuilder sb = new StringBuilder();
finalWords.forEach(word -> sb.append(word).append(","));
return sb.toString();
}
@Benchmark
public String stream() {
return finalWords.stream().collect(Collectors.joining(","));
}
}
Here are the results:
Benchmark (size) Mode Cnt Score Error Units
MyBenchmark.stream 10 avgt 30 242.330 ± 5.177 ns/op
MyBenchmark.stream 100 avgt 30 1426.333 ± 20.183 ns/op
MyBenchmark.stream 1000 avgt 30 30779.509 ± 1114.992 ns/op
MyBenchmark.stream 10000 avgt 30 720944.424 ± 27845.997 ns/op
MyBenchmark.stream 100000 avgt 30 7701294.456 ± 648084.759 ns/op
MyBenchmark.stringBuilder 10 avgt 30 170.566 ± 1.833 ns/op
MyBenchmark.stringBuilder 100 avgt 30 1166.153 ± 21.162 ns/op
MyBenchmark.stringBuilder 1000 avgt 30 32374.567 ± 979.288 ns/op
MyBenchmark.stringBuilder 10000 avgt 30 473022.229 ± 8982.260 ns/op
MyBenchmark.stringBuilder 100000 avgt 30 4524267.849 ± 242801.008 ns/op
As you can see, the StringBuilder
method is faster in this case, even when I don't specify an initial capacity.
String.join
. But you should probably benchmark. – EvanneStringBuilder
you can specify a capacity, inStringJoiner
you can't, it will grow*2
every time,8 -> 16 -> 32
array of Strings, these re-size useArray.copyOf
. I'd say with a proper capacityStringBuilder
would win in a speed test – PenoyerStringBuilder
so the difference will be insignificant. You can see some boiler plate code in thejoining
- all those new objects created (likeStringBuilder
) and all the if statements so it will be definitely a bit slower. Both approaches haveO(n)
complexity so it really comes down to what @ArnaudDenoyelle already said above - "prefer the most readable". – Devan","
which the joiner does not. Further, the first variant is not thread safe. Besides these differences, they do the same below the surface. So any performance difference stems from not doing the same thing. As said by Dukeling you may also useString.join(",", finalWords)
which is simpler (but also does basically the same)… – Ibrahim