I have the array ["a", "b", "c"] and I want to join them together to form "a#b#c#". String.join will only get me "a#b#c". I can't just do str += "#" either because that is slow (Java has to create a new string to do that). So instead I have to rewrite the whole thing using StringBuilder. Is there some method Java has that is basically String.join but also appends the delimiter to the end?
For a little more context, I'm trying to create a Suffix Array data structure with a group of strings, so this part of it is actually a bottleneck.
In Java, we can use String. join(",", list) to join a List String with commas.
join is not as a general replacement for + or String. concat , but in being the "reverse operation" of a String. split operation. It makes more sense in that context - when you have a bunch of strings that you want to join together using a delimiter - than as a replacement for concat .
You can use Stream API Collectors.joining() There are prefix and suffix arguments like that:
Stream.of("a", "b", "c")
.collect(Collectors.joining("#", "", "#"));
Where joining arguments is delimiter, prefix, suffix respectively.
Also, you can just add an empty String to your array like says @Wander Nauta in the comment above
I've started JMH, also and reproduce performance differences which wrote bellow:
public class Benchmarks {
private static final String[] arr = {"a", "b", "c"};
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.AverageTime)
public String joinAndConcat() {
return String.join("#", arr) + "#";
}
@Benchmark
@Fork(value = 1, warmups = 1)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public String streamsJoining() {
return Stream.of(arr)
.parallel()
.collect(Collectors.joining("#", "", "#"));
}
@Benchmark
@Fork(value = 1, warmups = 1)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public String stringJoiner() {
StringJoiner joiner = new StringJoiner("#", "", "#");
for (String el : arr) {
joiner.add(el);
}
return joiner.toString();
}
}
Results:
> Benchmark Mode Cnt Score Error Units
> Benchmarks.joinAndConcat avgt 5 46,670 ± 0,139 ns/op
> Benchmarks.streamsJoining avgt 5 73,336 ± 0,180 ns/op
> Benchmarks.stringJoiner avgt 5 27,236 ± 0,386 ns/op
But you must understand that 46 nSec is a very small difference for most applications.
Here are the performance measures using JMH:
Benchmark Mode Cnt Score Error Units
StringJoiner thrpt 100 23641181.522 ± 237176.955 ops/s
JoinAndConcat thrpt 100 14197523.377 ± 130873.538 ops/s
StreamsJoining thrpt 100 9538282.522 ± 156729.920 ops/s
The solution with streams takes around 2.5x longer compared to the solution with StringJoiner. In the midfield is as expected the join with concatenation (see question). However, we are talking about nanoseconds here.
A graphical overview showing the performance with recalculated values (ops/s => ns/op):

# JMH version: 1.32
# VM version: JDK 11.0.11, OpenJDK 64-Bit Server VM, 11.0.11+9
# VM invoker: /Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home/bin/java
# VM options: <none>
# Blackhole mode: full + dont-inline hint
# Warmup: 10 iterations, 1 s each
# Measurement: 10 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openjdk.jmh.annotations.Benchmark;
public class Benchmarks {
private static final String[] arr = {"a", "b", "c"};
@Benchmark
public String joinAndConcat() {
return String.join("#", arr) + "#";
}
@Benchmark
public String streamsJoining() {
return Stream.of(arr)
.collect(Collectors.joining("#", "", "#"));
}
@Benchmark
public String stringJoiner() {
StringJoiner joiner = new StringJoiner("#", "", "#");
for (String el : arr) {
joiner.add(el);
}
return joiner.toString();
}
}
String.join() uses StringJoiner behind the scenes so you can create one and specify suffix parameter as # in the constructor:
String[] array = { "a", "b", "c" };
StringJoiner joiner = new StringJoiner("#", "", "#");
for (String el : array) {
joiner.add(el);
}
System.out.println(joiner.toString()); // a#b#c#
However this feels like micro optimizing. Unless your joining code is critical it's way more readable to write:
String[] array = { "a", "b", "c" };
System.out.println(String.join("#", array) + "#"); // a#b#c#
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With