it-swarm.com.de

Kopieren Sie einen Stream, um zu vermeiden, dass der Stream bereits bearbeitet oder geschlossen wurde.

Ich möchte einen Java 8-Stream duplizieren, damit ich zweimal damit umgehen kann. Ich kann collect als Liste verwenden und daraus neue Streams abrufen.

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

Ich denke jedoch, dass es einen effizienteren/eleganteren Weg geben sollte.

Gibt es eine Möglichkeit, den Stream zu kopieren, ohne ihn in eine Sammlung umzuwandeln?

Ich arbeite gerade mit einem Stream von Eithers, also möchte ich die linke Projektion auf eine Weise verarbeiten, bevor auf die rechte Projektion übergegangen wird. Irgendwie so (was ich bisher gezwungen bin, den toList-Trick mit zu verwenden).

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

Ich glaube, Ihre Annahme über Effizienz ist eine Art Rückschritt. Sie erhalten diese enorme Effizienzsteigerung, wenn Sie die Daten nur einmal verwenden, da Sie sie nicht speichern müssen. Streams bieten Ihnen leistungsstarke "Loop Fusion" -Optimierungen, mit denen Sie die gesamten Daten effizient durch die Pipeline übertragen können. 

Wenn Sie dieselben Daten wiederverwenden möchten, müssen Sie sie definitionsgemäß entweder zweimal (deterministisch) generieren oder speichern. Wenn es schon zufällig in einer Sammlung ist, großartig; dann zweimal iterieren ist billig. 

Wir haben im Design mit "Forked Streams" experimentiert. Was wir fanden, war, dass die Unterstützung dies mit echten Kosten verbunden hätte; es belastet den allgemeinen Fall (einmalige Verwendung) auf Kosten des ungewöhnlichen Falles. Das Hauptproblem bestand darin, "was passiert, wenn die beiden Pipelines keine Daten mit der gleichen Rate verbrauchen". Jetzt bist du sowieso wieder im Puffer. Dies war eine Funktion, die offensichtlich nicht das Gewicht trug. 

Wenn Sie dieselben Daten wiederholt bearbeiten möchten, speichern Sie sie entweder oder strukturieren Sie Ihre Vorgänge als Verbraucher und führen Sie folgende Schritte aus:

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

Sie können auch in die RxJava-Bibliothek schauen, da sich ihr Verarbeitungsmodell besser für diese Art von "Stream-Forking" eignet.

76
Brian Goetz

Verwenden Sie Java.util.function.Supplier .

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

Streams wiederverwenden

Java 8-Streams können nicht wiederverwendet werden. Sobald Sie einen Terminalbetrieb aufrufen, wird der Stream geschlossen:

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

Das Aufrufen von noneMatch nach anyMatch für denselben Stream führt zu der folgenden Ausnahme:

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)

Um diese Einschränkung zu überwinden, müssen wir für jede Terminaloperation, die wir ausführen möchten, eine neue Stream-Kette erstellen, z. Wir könnten einen Stream-Anbieter erstellen, um einen neuen Stream mit allen bereits eingerichteten Zwischenvorgängen aufzubauen:

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

Jeder Aufruf von get() erstellt einen neuen Stream, in dem wir die gewünschte Terminaloperation aufrufen.

48
user4975679

Wir haben eine duplicate()-Methode für Streams in jOOλ implementiert, einer Open Source-Bibliothek, die wir zur Verbesserung der Integrationstests für jOOQ erstellt haben. Im Wesentlichen können Sie einfach schreiben:

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

Intern gibt es einen Puffer, der alle Werte speichert, die von einem Strom verbraucht wurden, aber nicht vom anderen. Das ist wahrscheinlich so effizient wie es nur geht, wenn Ihre zwei Streams etwa gleich hoch sind, und wenn Sie mit der mangelnden Threadsicherheit leben können.

So funktioniert der Algorithmus:

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()));
}

Mehr Quellcode hier

Tuple2 ist wahrscheinlich mit Ihrem Pair-Typ vergleichbar, wohingegen SeqStream mit einigen Verbesserungen ist.

8
Lukas Eder

Sie können zum Beispiel einen Stream von Runables erstellen:

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

Dabei sind failure und success die anzuwendenden Operationen. Dadurch werden jedoch einige temporäre Objekte erstellt, die möglicherweise nicht effizienter sind als das Starten einer Sammlung und das zweimalige Streaming/Iterieren.

7
assylias

Verwenden Sie den Lieferanten, um den Stream für jede Beendigungsoperation zu erstellen.

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

Wenn Sie einen Stream dieser Sammlung benötigen, verwenden Sie streamSupplier.get(), um einen neuen Stream abzurufen.

Beispiele:

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

Eine andere Möglichkeit, die Elemente mehrfach zu behandeln, ist die Verwendung von Stream.peek (Consumer) :

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

peek(Consumer) kann beliebig oft verkettet werden.

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-react , eine Bibliothek, an der ich mitarbeite, verfügt über eine statische Methode, mit der Sie einen Stream duplizieren können (und einen JOOλ-Tuple von Streams zurückgeben).

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

Siehe Kommentare, es gibt Leistungseinbußen, die bei der Verwendung von Duplikaten für einen vorhandenen Stream entstehen. Eine performantere Alternative wäre die Verwendung von Streamable: -

Es gibt auch eine (faule) Streamable-Klasse, die aus einem Stream, Iterable oder Array erstellt und mehrmals abgespielt werden kann.

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

AsStreamable.synchronizedFromStream (Stream) - kann verwendet werden, um ein Streamable zu erstellen, das die Sicherungssammlung träge auffüllt, sodass es von Threads gemeinsam genutzt werden kann. Streamable.fromStream (Stream) verursacht keinen Synchronisierungsaufwand. 

2
John McClean

Ich hatte ein ähnliches Problem und konnte mir drei verschiedene Zwischenstrukturen vorstellen, aus denen eine Kopie des Streams erstellt werden kann: eine List, ein Array und ein Stream.Builder. Ich schrieb ein kleines Benchmark-Programm, das anzeigte, dass die List aus Performance-Sicht etwa 30% langsamer war als die beiden anderen, die sich ziemlich ähnlich waren.

Der einzige Nachteil bei der Konvertierung in ein Array besteht darin, dass es schwierig ist, wenn Ihr Elementtyp ein generischer Typ ist (was in meinem Fall der Fall war). deshalb bevorzuge ich einen Stream.Builder.

Am Ende habe ich eine kleine Funktion geschrieben, die eine Collector erstellt:

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);
}

Ich kann dann eine Kopie eines beliebigen Streams str erstellen, indem ich str.collect(copyCollector()) mache, was sich im Einklang mit der idiomatischen Verwendung von Streams fühlt. 

0
Jeremy Hicks

Für dieses spezielle Problem können Sie auch die Partitionierung verwenden. So etwas wie

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

Wir können Stream Builder zum Zeitpunkt des Lesens oder Iterierens eines Streams verwenden. Hier ist das Dokument Stream Builder.

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

Anwendungsfall

Nehmen wir an, wir haben einen Mitarbeiter-Stream, und wir müssen diesen Stream verwenden, um Mitarbeiterdaten in eine Excel-Datei zu schreiben und dann die Mitarbeiter-Sammlung/Tabelle zu aktualisieren [Dies ist nur ein Anwendungsfall, um die Verwendung von Stream Builder zu zeigen.]

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