Search code examples
javajava-8java-stream

Collectors.joining vs StringBuilder.append


Which one is better in terms of performance?

finalWords.stream().forEach(word -> stringBuilder.append(word).append(”,“)); 
String finalResult = stringBuilder.toString();

VS

String finalResult = finalWords.stream().collect(Collectors.joining(","));

Solution

  • I put together a small benchmark to test this out because I was curious. It initializes the List with size randomly-generated lowercase Strings, 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.