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

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

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

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

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

Я на самом деле работаю с потоком Either s, поэтому хочу обработать левую проекцию в одну сторону, прежде чем двигаться на правый проектор и иметь дело с этим другим способом. Вид вроде этого (который, до сих пор, я вынужден использовать трюк 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 -> ... );

Ответ 1

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

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

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

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

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

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

Ответ 2

Используйте 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

Вызов noMatch после 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() создает новый поток, который мы сохраняем, чтобы вызвать требуемую операцию терминала.

Ответ 3

Мы реализовали метод 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 с некоторыми улучшениями.

Ответ 4

Вы можете создать поток runnables (например):

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

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

Ответ 5

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

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

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

Примеры:

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

Ответ 6

Еще один способ обработки элементов несколько раз - использовать 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()));

Ответ 7

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

    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(поток) - может использоваться для создания Streamable, который будет лениво заполнять его, поддерживая коллекцию, таким образом, чтобы они могли делиться между потоками. Streamable.fromStream(поток) не будет нести накладные расходы на синхронизацию.

Ответ 8

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

     // 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)

Ответ 9

Мы можем использовать 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();

Ответ 10

У меня была похожая проблема, и я мог подумать о трех различных промежуточных структурах, из которых можно создать копию потока: 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()) который вполне соответствует идиоматическому использованию потоков.

Ответ 11

Мы, ООО ПРОМ ТРЕЙДИНГ, являемся одной из ведущих нефтегазовых торговых компаний в Российской Федерации с хорошей деловой репутацией и большим опытом работы в нефтегазовом секторе. Мы предлагаем следующие сделки через наши надежные нефтеперерабатывающие заводы: D2 ДИЗЕЛЬНОЕ МАСЛО ГОСТ 305-82, JP54 АВИАЦИОННАЯ КЕРОЗЕНОВАЯ КОЛОНИЯ, UREA 46%/ПРИЛЛЫ, СПГ, СНГ, REBCO, МАЗУТ100 ГОСТ 10585-75/99, АВТОМОБИЛЬНОЕ ГАЗОВОЕ МАСЛО (АГО) И Т.П. Мы также обеспечиваем отчисления от наших различных НПЗ нашим многочисленным покупателям, которые заинтересованы в спотовых сделках по доставке FOB/CIF в любой безопасный порт мира (AWSP). Наши нефтеперерабатывающие заводы имеют свою продукцию как в российских портах, так и в порту Роттердам.

С уважением

Андрей Анкудимов

Тел: +7 (987) 897-72-99

Электронная почта: [email protected]

Skype: serviceandrey.a