简体   繁体   中英

Java String.join but also add delimiter to the end

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.

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#

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM