Как разбить строку на поток строк?

Что такое лучший метод разделения строки на поток?

Я видел эти варианты:

  • Arrays.stream("b,l,a".split(","))
  • Stream.of("b,l,a".split(","))
  • Pattern.compile(",").splitAsStream("b,l,a")

Мои приоритеты:

  • Надёжность
  • читабельность
  • Производительность

Полный, компилируемый пример:

import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class HelloWorld {

    public static void main(String[] args) {
        stream1().forEach(System.out::println);
        stream2().forEach(System.out::println);
        stream3().forEach(System.out::println);
    }

    private static Stream<String> stream1() {
        return Arrays.stream("b,l,a".split(","));
    }

    private static Stream<String> stream2() {
        return Stream.of("b,l,a".split(","));
    }

    private static Stream<String> stream3() {
        return Pattern.compile(",").splitAsStream("b,l,a");
    }

}

Ответ 1

String.split

Ну, поскольку String.split возвращает массив String[], я всегда рекомендую Arrays.stream в качестве канонической идиомы для потоковой передачи по массиву.

String input = "dog,cat,bird";
Stream< String > stream = Arrays.stream( input.split( "," ) );
stream.forEach( System.out :: println );

Stream.of

Stream.of - это метод varargs, который просто принимает массив, из-за того, что методы varargs реализованы через массивы, и были проблемы с совместимостью, когда varargs были введены в Java и существующие методы были модифицированы для приема переменных аргументов.

Stream< String > stream = Stream.of( input.split( "," ) ); // works, but is non-idiomatic
Stream< String > stream = Stream.of( "dog", "cat", "bird" ); // intended use case

Pattern.compile

Pattern.compile(",").splitAsStream(string) имеет преимущество прямой потоковой передачи, а не создания промежуточного массива. Таким образом, для большого количества подстрок это может иметь преимущество в производительности. С другой стороны, если разделитель является тривиальным, то есть одним литеральным символом, реализация String.split пройдет быстрый путь вместо использования механизма регулярных выражений. Так что в этом случае ответ не тривиален.

Если потоковая передача происходит внутри другого потока, например, .flatMap(Pattern.compile(pattern) ::splitAsStream) есть преимущество в том, что шаблон должен анализироваться только один раз, а не для каждой строки внешнего потока.

Stream< String > stream = Pattern.compile( "," ).splitAsStream( input );

Ответ 2

Относительно (1) и (2) не должно быть большой разницы, так как ваш код почти одинаковый.
Что касается (3), это было бы гораздо более эффективным в плане использования памяти (не обязательно CPU), но, на мой взгляд, немного сложнее читать.

Ответ 3

  Надёжность

Я не вижу разницы в надежности трех подходов.

Читаемость

Мне не известны какие-либо заслуживающие доверия научные исследования в области читабельности кода с участием опытных программистов на Java, поэтому читаемость - это вопрос мнения. Даже тогда вы никогда не узнаете, проводит ли кто-то, высказывающий свое мнение, объективное различие между фактической читаемостью, тем, чему его учили о читабельности, и собственным вкусом.

Поэтому я оставлю вам право самостоятельно оценивать удобочитаемость... отмечая, что вы считаете это высоким приоритетом.

FWIW, единственные люди, чье мнение по этому вопросу, это вы и ваша команда.

Производительность

Я думаю, что ответом на это является тщательный сравнительный анализ трех альтернатив. Хольгер дает анализ, основанный на его изучении некоторых версий Java. Но:

  1. Он не смог прийти к определенному выводу, который был самым быстрым.
  2. Строго говоря, его анализ относится только к тем версиям Java, на которые он смотрел. (Некоторые аспекты его анализа могут отличаться в (скажем) Android Java или в некоторых будущих версиях Oracle/OpenJDK.)
  3. Относительная производительность, скорее всего, зависит от длины разделяемой строки, количества полей и сложности регулярного выражения разделителя.
  4. В реальном приложении относительная производительность также может зависеть от того, что вы делаете с объектом Stream, какой сборщик мусора вы выбрали (поскольку разные версии, по-видимому, генерируют различное количество мусора) и другие проблемы.

Поэтому, если вы (или кто-то еще) действительно обеспокоены производительностью, вы должны написать микро-тест и запустить его на своей производственной платформе (-ах). Затем сделайте некоторые тесты для конкретного приложения. И вы должны рассмотреть возможность поиска решений, которые не включают потоки.