Ich suche nach einer Möglichkeit, eine nicht terminale Gruppierungsoperation zu implementieren, so dass der Speicheraufwand minimal ist.
Betrachten Sie zum Beispiel distinct (). Im allgemeinen Fall hat es keine andere Wahl, als alle eindeutigen Elemente zu sammeln und sie dann nur vorwärts zu streamen. Wenn wir jedoch wissen, dass der Eingabestrom bereits sortiert ist, kann die Operation "on-the-fly" durchgeführt werden, wobei nur wenig Speicher benötigt wird.
Ich weiß, dass ich dies für Iteratoren mit einem Iterator-Wrapper erreichen und die Gruppierungslogik selbst implementieren kann. Gibt es eine einfachere Möglichkeit, dies zu implementieren, indem Sie stattdessen die Stream-API verwenden?
- BEARBEITEN -
Ich habe einen Weg gefunden, Stream.flatMap (..) zu missbrauchen, um dies zu erreichen:
%Vor%Und dann:
%Vor%Welche Drucke:
%Vor%Mit einigen Änderungen kann dieselbe Technik für jede Art von speichereffizienter Sequenzgruppierung von Strömen verwendet werden. Wie auch immer, ich mag diese Lösung nicht sehr, und ich suchte nach etwas Natürlicherem (wie zum Beispiel das Mapping oder Filtering). Außerdem breche ich hier den Vertrag, weil die Funktion, die flatMap (..) zur Verfügung stellt, Stateful ist.
Wenn Sie eine Lösung möchten, die einer Funktion, die sie nicht haben soll, keinen änderbaren Status hinzufügt, können Sie auf collect
:
Dies funktioniert so, wie es für die Verwendung von veränderbaren Containern vorgesehen ist, es kann jedoch nicht parallel arbeiten, da das Teilen an beliebigen Stream-Positionen die Möglichkeit beinhaltet, einen Wert in zwei (oder sogar mehr) Threads zu finden >
Wenn Sie eine allgemeine IntStream
-Aktion anstelle einer forEach
-Aktion wünschen, wird eine Spliterator
-Low-Level-Lösung trotz der zusätzlichen Komplexität bevorzugt.
Es erbt sogar eine parallele Unterstützung, obwohl es funktioniert, indem es einige Werte vor dem Verarbeiten in einem anderen Thread vorfabriziert, so dass es die distinct -Operation nicht beschleunigt, aber möglicherweise Folgeoperationen, wenn es Berechnungen gibt intensive.
Zur Vervollständigung ist hier eine eindeutige Operation für beliebige, d. h. unsortierte, IntStream
s, die nicht auf "boxen plus HashMap
" angewiesen ist und daher einen viel besseren Speicherbedarf haben kann:
Es funktioniert nur für positive int
-Werte. die Erweiterung auf den vollen 32-Bit-Bereich würde zwei BitSet
s erfordern, also nicht so prägnant aussehen, aber oft erlaubt der Anwendungsfall, den Speicher auf den 31-Bit-Bereich oder sogar niedriger zu begrenzen ...
Die richtige Vorgehensweise besteht darin, den Stream in einen Spliterator umzuwandeln und ihn dann abhängig von den Eigenschaften des zurückgegebenen Spliterators umzubrechen.
trySplit
operations wird schwierig sein, da sie den Sub-Spliterator um ein paar Schritte vorrücken muss, bis er sicher ist, dass er nicht den Schwanz eines Lauf von nicht eindeutigen Elementen. Sobald Sie diesen Spliterator haben, können Sie ihn wieder in einen Stream mit den gleichen Eigenschaften umwandeln und weiterhin auf ihm streamen
Da wir vorhandene jdk-Schnittstellen nicht modifizieren können, müsste die Helfer-API eher so aussehen: dedup(IntStream.of(...).map(...)).collect(...)
.
Wenn Sie die Quelle von java.util.stream.DistinctOps.makeRef(AbstractPipeline<?, T, ?>)
untersuchen, werden Sie feststellen, dass das JDK dies mehr oder weniger für referenzbasierte Streams tut.
Es ist nur die IntStream-Implementierung ( java.util.stream.IntPipeline.distinct()
), die einen ineffizienten Ansatz verwendet, der nicht von DISTINCT
oder SORTED
profitiert.
Er konvertiert nur blind einen IntStream in einen umrandeten Integer
-Stream und verwendet die referenzbasierte Deduplizierung, ohne die entsprechenden Flags zu übergeben, die ihn speichereffizient machen würden.
Wenn dies in jdk9 nicht bereits behoben ist, könnte es einen Fehler wert sein, da es im Grunde unnötig Speicherverbrauch und verschwendete Optimierungspotential für die Stream-Ops ist, wenn sie Stream-Flags unnötig verwerfen.
Tags und Links java java-8 java-stream