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