Java 8 - Как создать строку из начальной строки только с одним обходом - PullRequest
0 голосов
/ 14 ноября 2018

У меня есть URL-адрес, например: String url = "https://.../foo/a/555/data1";

Цель : преобразовать URL-адрес в строку: a555data1

Я хочу получить этот результат, пройдя поСтрока только один раз.Я решил для следующего процесса:

  1. Я хочу "поток" строки, начиная со спины.
  2. Если это не вставка / добавление с обратной косой чертой в начале deque.
  3. Если это третий конец обратной косой черты

Я успешно написал ужасное решение ниже, можно ли сделать его довольно красивым, используя потоки?

Deque<String> lifo = new ArrayDeque<>();

int count = 0;
for (int i = testUrl.length() - 1; count < 3 ; --i) {
    if (testUrl.codePointAt(i) == ((int) '/') ) {
        ++count;
        continue;
    }

    result.addFirst(testUrl.substring(i,i+1));

}

String foo = result.stream().collect(Collectors.joining());
assertThat(foo).isEqualTo("a606KAM1");

Ответы [ 6 ]

0 голосов
/ 14 ноября 2018

Если вы хотите сделать это очень быстро, вы должны уменьшить объем копирования данных, происходящий при каждой конструкции строки.

int ix1 = url.lastIndexOf('/'), ix2 = url.lastIndexOf('/', ix1-1),
    ix3 = url.lastIndexOf('/', ix2-1);
String result = new StringBuilder(url.length() - ix3 - 3)
    .append(url, ix3+1, ix2)
    .append(url, ix2+1, ix1)
    .append(url, ix1+1, url.length())
    .toString();

Даже когда вы расширяете его для поддержки настраиваемого количества деталей,

int chunks = 3;
int[] ix = new int[chunks];
int index = url.length();
for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
StringBuilder sb = new StringBuilder(url.length() - index - chunks);
for(int next: ix) sb.append(url, index+1, index = next);
String result = sb.toString();

это, вероятно, быстрее, чем все альтернативы.

0 голосов
/ 14 ноября 2018

Первоначально я думал, что потоки не должны использоваться здесь из-за предполагаемой потери производительности, поэтому я создал небольшой тест производительности для решений, предложенных в других ответах:

import static java.util.Arrays.stream;

import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1)
public class CJMH {

    @State(Scope.Thread)
    public static class CState {
        public String url = "https://.../foo/a/555/data1";
    }

    @Benchmark
    public String fastest(CState state) {
        String url = state.url;
        int chunks = 3;
        int[] ix = new int[chunks];
        int index = url.length();
        for(int a = ix.length-1; a >= 0; a--) index = url.lastIndexOf('/', (ix[a] = index)-1);
        StringBuilder sb = new StringBuilder(url.length() - index - chunks);
        for(int next: ix) sb.append(url, index+1, index = next);
        return sb.toString();
    }

    @Benchmark
    public String splitAndStreams(CState state) {
        final String[] splitStrArr = state.url.split("/");
        String result = stream(splitStrArr).
                skip(splitStrArr.length - 3).
                collect(Collectors.joining(""));

        return result;
    };

    @Benchmark
    public String splitAndIterate(CState state) {
        final String[] splitStrArr = state.url.split("/");

        String result = "";
        for (int k=splitStrArr.length - 3; k<splitStrArr.length; k++) {
            result += splitStrArr[k];
        }

        return result;
    };

    @Benchmark
    public String splitAndSum(CState state) {
        String[] split = state.url.split("/");
        int n = split.length;
        return split[n - 3] + split[n - 2] + split[n - 1];
    };

    @Benchmark
    public String regexp(CState state) {
        return state.url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
    };
}

И вывод был:

Benchmark             Mode  Cnt    Score    Error  Units
CJMH.fastest          avgt    5   46.731 ±  0.445  ns/op
CJMH.regexp           avgt    5  937.797 ± 11.928  ns/op
CJMH.splitAndIterate  avgt    5  194.626 ±  1.880  ns/op
CJMH.splitAndStreams  avgt    5  275.640 ±  1.887  ns/op
CJMH.splitAndSum      avgt    5  180.257 ±  2.986  ns/op

Так что на удивление потоки ни в коем случае не намного медленнее, чем итерации по массиву. Самый быстрый - это алгоритм отсутствия копирования, предоставленный @Holger в этом ответе . И не используйте регулярные выражения, если вы можете избежать этого!

0 голосов
/ 14 ноября 2018

Другим способом было бы регулярное выражение:

String result = url.replaceAll(".+/(.+)/(.+)/(.+)", "$1$2$3");
0 голосов
/ 14 ноября 2018

Я бы, наверное, немного упростил ваш код до:

StringBuilder sb = new StringBuilder();

    char c;
    for (int i = testUrl.length() - 1, count = 0; count < 3 ; --i) {
        if ((c = testUrl.charAt( i )) ==  '/') {
            ++count;
            continue;
        }
        sb.append( c );
    }
    String foo = sb.reverse().toString();

По моему мнению, здесь нет никакого смысла в использовании потока - URL-адрес недостаточно длинный, чтобы оправдать затраты времени на настройку потока. Также мы можем использовать StringBuilder - который будет использоваться при любом присоединении.

0 голосов
/ 14 ноября 2018

Альтернативное решение без петель и потоков:

String[] split = url.split("/");
int n = split.length;
return split[n - 3] + split[n - 2] + split[n - 1];
0 голосов
/ 14 ноября 2018

Вы можете сделать это так,

final String[] splitStrArr = url.split("/");
String result = Arrays.stream(splitStrArr).skip(splitStrArr.length - 3)
                    .collect(Collectors.joining(""));
...