it-swarm.com.ru

Могу ли я продублировать поток в Java 8?

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

Могу ли я сделать это без указания общих начальных операций дважды?

Например, я надеюсь, что существует метод dup(), такой как:

Stream [] desired_streams = IntStream.range(1, 100).filter(n -> n % 2 == 0).dup();
Stream stream14 = desired_streams[0].filter(n -> n % 7 == 0); // multiples of 14
Stream stream10 = desired_streams[1].filter(n -> n % 5 == 0); // multiples of 10
46
necromancer

Это невозможно вообще.

Если вы хотите дублировать поток ввода или итератор ввода, у вас есть два варианта:

A. Храните все в коллекции, скажем, List<>

Предположим, вы дублируете поток в два потока s1 и s2. Если у вас есть расширенные элементы n1 в элементах s1 и n2 с s2, вы должны хранить элементы |n2 - n1| в памяти, просто чтобы идти в ногу. Если ваш поток бесконечный, может не быть верхней границы для требуемого хранилища.

Взгляните на Python tee() , чтобы увидеть, что нужно:

Этот itertool может потребовать значительного вспомогательного хранения (в зависимости от того, сколько временных данных необходимо сохранить). В общем, если один итератор использует большую часть или все данные до запуска другого итератора, быстрее использовать list() вместо tee().

B. Когда возможно: скопируйте состояние генератора, который создает элементы

Чтобы эта опция работала, вам, вероятно, понадобится доступ к внутренней работе потока. Другими словами, генератор - часть, которая создает элементы - должен в первую очередь поддерживать копирование. [OP: Смотрите это отличный ответ , как пример того, как это можно сделать для примера в вопросе]

Он не будет работать при вводе данных пользователем, поскольку вам придется копировать состояние всего «внешнего мира». Java Stream не поддерживает копирование, поскольку оно разработано так, чтобы быть максимально общим, особенно для работы с файлами, сетью, клавиатурой, датчиками, случайностью и т.д. [OP: Другой пример - поток, который читает датчик температуры по требованию. Это не может быть продублировано без сохранения копии показаний]

Это не только случай в Java; это общее правило. Вы можете видеть, что std::istream в C++ поддерживает только семантику перемещения, но не семантику копирования («конструктор копирования (удаленный)»), по этой причине (и другим). 

26
Elazar

Таким способом невозможно дублировать поток. Однако вы можете избежать дублирования кода, переместив общую часть в метод или лямбда-выражение.

Supplier<IntStream> supplier = () ->
    IntStream.range(1, 100).filter(n -> n % 2 == 0);
supplier.get().filter(...);
supplier.get().filter(...);
39
nosid

Это возможно, если вы буферизуете элементы, которые вы использовали в одном дубликате, но еще не в другом.

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

Tuple2<Seq<Integer>, Seq<Integer>> desired_streams = Seq.seq(
    IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed()
).duplicate();

(примечание: в настоящее время нам нужно заблокировать поток, так как мы еще не реализовали переменную IntSeq)

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

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

static <T> Tuple2<Seq<T>, Seq<T>> duplicate(Stream<T> stream) {
    final LinkedList<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()));
}

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

Фактически, используя jOOλ , вы сможете написать полный однострочный текст примерно так:

Tuple2<Seq<Integer>, Seq<Integer>> desired_streams = Seq.seq(
    IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed()
).duplicate()
 .map1(s -> s.filter(n -> n % 7 == 0))
 .map2(s -> s.filter(n -> n % 5 == 0));

// This will yield 14, 28, 42, 56...
desired_streams.v1.forEach(System.out::println)

// This will yield 10, 20, 30, 40...
desired_streams.v2.forEach(System.out::println);
6
Lukas Eder

Вы также можете переместить генерацию потока в отдельный метод/функцию, которая возвращает этот поток, и вызвать его дважды.

4
Tomasz Górka

Или,

  • Переместите инициализацию в метод и просто вызовите метод снова

Преимущество этого в том, что вы явно говорите о том, что вы делаете, а также работает для бесконечных потоков.

  • Соберите поток и затем повторно направьте его

В вашем примере:

final int[] arr = IntStream.range(1, 100).filter(n -> n % 2 == 0).toArray();

Затем

final IntStream s = IntStream.of(arr);
3
Boris the Spider

Обновление: Это не / работает. См. Объяснение ниже, после текста оригинального ответа.

Как глупо с моей стороны. Все, что мне нужно сделать, это:

Stream desired_stream = IntStream.range(1, 100).filter(n -> n % 2 == 0);
Stream stream14 = desired_stream.filter(n -> n % 7 == 0); // multiples of 14
Stream stream10 = desired_stream.filter(n -> n % 5 == 0); // multiples of 10

Объяснение, почему это не работает:

Если вы закодируете его и попытаетесь собрать оба потока, первый будет собираться нормально, но при попытке выполнить потоковую передачу будет выдано исключение: Java.lang.IllegalStateException: stream has already been operated upon or closed.

Чтобы уточнить, потоки являются объектами с состоянием (которые, кстати, не могут быть сброшены или перемотаны). Вы можете думать о них как об итераторах, которые, в свою очередь, похожи на указатели. Таким образом, stream14 и stream10 можно рассматривать как ссылки на один и тот же указатель. Полное использование первого потока приведет к тому, что указатель пойдет «мимо конца». Попытка использовать второй поток - это все равно, что пытаться получить доступ к указателю, который уже «за пределами конца», что, естественно, является недопустимой операцией.

Как показывает принятый ответ, код для создания потока должен выполняться дважды, но его можно разделить на лямбду Supplier или аналогичную конструкцию.

Полный тестовый код: сохранить в Foo.Java, затем javac Foo.Java, затем Java Foo

import Java.util.stream.IntStream;

public class Foo {
  public static void main (String [] args) {
    IntStream s = IntStream.range(0, 100).filter(n -> n % 2 == 0);
    IntStream s1 = s.filter(n -> n % 5 == 0);
    s1.forEach(n -> System.out.println(n));
    IntStream s2 = s.filter(n -> n % 7 == 0);
    s2.forEach(n -> System.out.println(n));
  }
}

Результат:

$ javac Foo.Java
$ Java Foo
0
10
20
30
40
50
60
70
80
90
Exception in thread "main" Java.lang.IllegalStateException: stream has already been operated upon or closed
    at Java.util.stream.AbstractPipeline.<init>(AbstractPipeline.Java:203)
    at Java.util.stream.IntPipeline.<init>(IntPipeline.Java:91)
    at Java.util.stream.IntPipeline$StatelessOp.<init>(IntPipeline.Java:592)
    at Java.util.stream.IntPipeline$9.<init>(IntPipeline.Java:332)
    at Java.util.stream.IntPipeline.filter(IntPipeline.Java:331)
    at Foo.main(Foo.Java:8)
2
necromancer

Для бесконечных потоков, если у вас есть доступ к источнику, это прямо:

@Test
public void testName() throws Exception {
    List<Integer> integers = Arrays.asList(1, 2, 4, 5, 6, 7, 8, 9, 10);
    Stream<Integer> stream1 = integers.stream();
    Stream<Integer> stream2 = integers.stream();

    stream1.forEach(System.out::println);
    stream2.forEach(System.out::println);
}

печать

1 2 4 5 6 7 8 9 10

1 2 4 5 6 7 8 9 10

Для вашего случая:

Stream originalStream = IntStream.range(1, 100).filter(n -> n % 2 == 0)

List<Integer> listOf = originalStream.collect(Collectors.toList())

Stream stream14 = listOf.stream().filter(n -> n % 7 == 0);
Stream stream10 = listOf.stream().filter(n -> n % 5 == 0);

Для производительности и т.д. Читайте чужой ответ;)

0
Blundell