it-swarm.com.ru

Скопируйте поток, чтобы избежать "поток уже был обработан или закрыт"

Я хотел бы продублировать поток Java 8, чтобы справиться с ним дважды. Я могу collect как список и получать новые потоки из этого;

// doSomething() returns a stream
List<A> thing = doSomething().collect(toList());
thing.stream()... // do stuff
thing.stream()... // do other stuff

Но я думаю, что должен быть более эффективный/элегантный способ.

Есть ли способ скопировать поток, не превращая его в коллекцию?

Я на самом деле работаю с потоком Eithers, поэтому хочу обработать левую проекцию в одну сторону, прежде чем перейти к правой проекции и разобраться с этим другим способом. Вроде как (что до сих пор я вынужден использовать трюк toList).

List<Either<Pair<A, Throwable>, A>> results = doSomething().collect(toList());

Stream<Pair<A, Throwable>> failures = results.stream().flatMap(either -> either.left());
failures.forEach(failure -> ... );

Stream<A> successes = results.stream().flatMap(either -> either.right());
successes.forEach(success -> ... );
84
Toby

Я думаю, что ваше предположение об эффективности является своего рода задом наперед. Вы получаете эту огромную отдачу от эффективности, если собираетесь использовать данные только один раз, потому что вам не нужно их хранить, а потоки дают вам мощную оптимизацию «объединения цикла», которая позволяет эффективно передавать все данные по конвейеру. 

Если вы хотите повторно использовать одни и те же данные, то по определению вы должны либо сгенерировать их дважды (детерминистически), либо сохранить их. Если он уже находится в коллекции, отлично; тогда повторение этого дважды дешево. 

Мы провели эксперимент в дизайне с «раздвоенными потоками». То, что мы обнаружили, было то, что поддержка этого имела реальные затраты; это обременяло общий случай (использовать один раз) за счет необычного случая. Большая проблема заключалась в том, «что происходит, когда два конвейера не используют данные с одинаковой скоростью». Теперь вы все равно вернетесь к буферизации. Это была особенность, которая явно не имела своего веса. 

Если вы хотите работать с одними и теми же данными несколько раз, либо сохраните их, либо структурируйте свои операции как потребители и выполните следующие действия:

stream()...stuff....forEach(e -> { consumerA(e); consumerB(e); });

Вы также можете заглянуть в библиотеку RxJava, так как ее модель обработки лучше подходит для такого типа «разветвления потока».

76
Brian Goetz

Используйте Java.util.function.Supplier .

От http://winterbe.com/posts/2014/07/31/Java8-stream-tutorial-examples/ :

Повторное использование потоков

Потоки Java 8 не могут быть повторно использованы. Как только вы вызываете любую терминальную операцию, поток закрывается:

Stream<String> stream =

Stream.of("d2", "a2", "b1", "b3", "c")

.filter(s -> s.startsWith("a"));

stream.anyMatch(s -> true);    // ok

stream.noneMatch(s -> true);   // exception

Вызов noneMatch после anyMatch в том же потоке приводит к следующему исключению:

Java.lang.IllegalStateException: stream has already been operated upon or closed

at 

Java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.Java:229)

at 

Java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.Java:459)

at com.winterbe.Java8.Streams5.test7(Streams5.Java:38)

at com.winterbe.Java8.Streams5.main(Streams5.Java:28)

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

Supplier<Stream<String>> streamSupplier =

    () -> Stream.of("d2", "a2", "b1", "b3", "c")

            .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok

streamSupplier.get().noneMatch(s -> true);  // ok

Каждый вызов get() создает новый поток, в котором мы сохраняемся для вызова требуемой операции терминала.

48
user4975679

Мы реализовали метод duplicate() для потоков в jOOλ , библиотеке с открытым исходным кодом, которую мы создали, чтобы улучшить интеграционное тестирование для jOOQ . По сути, вы можете просто написать:

Tuple2<Seq<A>, Seq<A>> duplicates = Seq.seq(doSomething()).duplicate();

Внутри есть буфер, в котором хранятся все значения, которые были использованы из одного потока, но не из другого. Это, вероятно, столь же эффективно, как и в случае, если ваши два потока потребляются примерно с одинаковой скоростью, и если вы можете жить с отсутствием безопасности потоков.

Вот как работает алгоритм:

static <T> Tuple2<Seq<T>, Seq<T>> duplicate(Stream<T> stream) {
    final List<T> gap = new LinkedList<>();
    final Iterator<T> it = stream.iterator();

    @SuppressWarnings("unchecked")
    final Iterator<T>[] ahead = new Iterator[] { null };

    class Duplicate implements Iterator<T> {
        @Override
        public boolean hasNext() {
            if (ahead[0] == null || ahead[0] == this)
                return it.hasNext();

            return !gap.isEmpty();
        }

        @Override
        public T next() {
            if (ahead[0] == null)
                ahead[0] = this;

            if (ahead[0] == this) {
                T value = it.next();
                gap.offer(value);
                return value;
            }

            return gap.poll();
        }
    }

    return Tuple(seq(new Duplicate()), seq(new Duplicate()));
}

Больше исходного кода здесь

Tuple2, вероятно, похож на ваш тип Pair, тогда как Seq является Stream с некоторыми улучшениями.

8
Lukas Eder

Вы можете создать поток исполняемых файлов (например):

results.stream()
    .flatMap(either -> Stream.<Runnable> of(
            () -> failure(either.left()),
            () -> success(either.right())))
    .forEach(Runnable::run);

Где failure и success - применяемые операции. Это, однако, создаст довольно много временных объектов и может быть не более эффективным, чем запуск из коллекции и ее потоковая/итерация дважды.

7
assylias

Используйте поставщика для создания потока для каждой операции завершения.

Supplier <Stream<Integer>> streamSupplier=()->list.stream();

Всякий раз, когда вам нужен поток из этой коллекции, Используйте streamSupplier.get(), чтобы получить новый поток.

Примеры:

  1. streamSupplier.get().anyMatch(predicate);
  2. streamSupplier.get().allMatch(predicate2);
4
Rams

Другой способ обрабатывать элементы несколько раз - использовать Stream.peek (Consumer) :

doSomething().stream()
.peek(either -> handleFailure(either.left()))
.foreach(either -> handleSuccess(either.right()));

peek(Consumer) может быть соединен столько раз, сколько необходимо.

doSomething().stream()
.peek(element -> handleFoo(element.foo()))
.peek(element -> handleBar(element.bar()))
.peek(element -> handleBaz(element.baz()))
.foreach(element-> handleQux(element.qux()));
3
Martin

cyclops-реагировать , библиотека, в которую я внес вклад, имеет статический метод, который позволит вам дублировать поток (и возвращает jOOλ кортеж потоков).

    Stream<Integer> stream = Stream.of(1,2,3);
    Tuple2<Stream<Integer>,Stream<Integer>> streams =  StreamUtils.duplicate(stream);

См. Комментарии, при использовании дубликата в существующем потоке будет наблюдаться снижение производительности. Более эффективной альтернативой было бы использование Streamable:

Существует также (ленивый) класс Streamable, который может быть создан из Stream, Iterable или Array и воспроизведен несколько раз.

    Streamable<Integer> streamable = Streamable.of(1,2,3);
    streamable.stream().forEach(System.out::println);
    streamable.stream().forEach(System.out::println);

AsStreamable.synchronizedFromStream (stream) - может использоваться для создания Streamable, который будет лениво заполнять свою резервную коллекцию таким образом, чтобы ее можно было разделить между потоками. Streamable.fromStream (stream) не повлечет за собой никаких накладных расходов на синхронизацию. 

2
John McClean

У меня была похожая проблема, и я мог подумать о трех различных промежуточных структурах, из которых можно создать копию потока: List, массив и Stream.Builder. Я написал небольшую тестовую программу, в которой предполагалось, что с точки зрения производительности List был примерно на 30% медленнее, чем две другие, которые были довольно похожими.

Единственный недостаток преобразования в массив состоит в том, что сложно, если ваш тип элемента является универсальным типом (что в моем случае было); поэтому я предпочитаю использовать Stream.Builder.

В итоге я написал небольшую функцию, которая создает Collector:

private static <T> Collector<T, Stream.Builder<T>, Stream<T>> copyCollector()
{
    return Collector.of(Stream::builder, Stream.Builder::add, (b1, b2) -> {
        b2.build().forEach(b1);
        return b1;
    }, Stream.Builder::build);
}

Затем я могу сделать копию любого потока str, выполнив str.collect(copyCollector()), что вполне соответствует идиоматическому использованию потоков. 

0
Jeremy Hicks

Для этой конкретной проблемы вы можете использовать также разбиение. Что-то вроде

     // Partition Eighters into left and right
     List<Either<Pair<A, Throwable>, A>> results = doSomething();
     Map<Boolean, Object> passingFailing = results.collect(Collectors.partitioningBy(s -> s.isLeft()));
     passingFailing.get(true) <- here will be all passing (left values)
     passingFailing.get(false) <- here will be all failing (right values)
0
Lubomir Varga

Мы можем использовать Stream Builder во время чтения или итерации потока . Вот документ Stream Builder.

https://docs.Oracle.com/javase/8/docs/api/Java/util/stream/Stream.Builder.html

Вариант использования

Допустим, у нас есть поток сотрудников, и нам нужно использовать этот поток для записи данных сотрудников в файл Excel, а затем обновить коллекцию/таблицу сотрудников [Это просто пример использования, показывающий использование Stream Builder]:

Stream.Builder<Employee> builder = Stream.builder();

employee.forEach( emp -> {
   //store employee data to Excel file 
   // and use the same object to build the stream.
   builder.add(emp);
});

//Now this stream can be used to update the employee collection
Stream<Employee> newStream = builder.build();
0
Lokesh Singal