Wir hatten ein ähnliches Problem zu lösen. Wir wollten einen Stream nehmen, der größer als der Systemspeicher ist (alle Objekte in einer Datenbank durchlaufen) und die Reihenfolge so gut wie möglich zufällig sortieren - wir dachten, es wäre in Ordnung, 10.000 Elemente zu puffern und zufällig zu sortieren.
Das Ziel war eine Funktion, die einen Strom aufnahm.
Von den hier vorgeschlagenen Lösungen scheint es eine Reihe von Optionen zu geben:
- Verwenden Sie verschiedene zusätzliche Bibliotheken, die nicht von Java 8 stammen
- Beginnen Sie mit etwas, das kein Stream ist - z. B. einer Liste mit wahlfreiem Zugriff
- Haben Sie einen Strom, der leicht in einem Spliterator geteilt werden kann
Unser Instinkt war ursprünglich, einen benutzerdefinierten Sammler zu verwenden, aber dies bedeutete, das Streaming zu beenden. Die oben beschriebene benutzerdefinierte Kollektorlösung ist sehr gut und wir haben sie fast verwendet.
Hier ist eine Lösung, die betrügt, indem sie die Tatsache nutzt, dass Stream
s Ihnen eine geben kann, Iterator
die Sie als Notluke verwenden können , damit Sie etwas extra tun können, das Streams nicht unterstützen. Das Iterator
wird mit einem anderen Stück Java 8- StreamSupport
Zauberei wieder in einen Stream konvertiert .
/**
* An iterator which returns batches of items taken from another iterator
*/
public class BatchingIterator<T> implements Iterator<List<T>> {
/**
* Given a stream, convert it to a stream of batches no greater than the
* batchSize.
* @param originalStream to convert
* @param batchSize maximum size of a batch
* @param <T> type of items in the stream
* @return a stream of batches taken sequentially from the original stream
*/
public static <T> Stream<List<T>> batchedStreamOf(Stream<T> originalStream, int batchSize) {
return asStream(new BatchingIterator<>(originalStream.iterator(), batchSize));
}
private static <T> Stream<T> asStream(Iterator<T> iterator) {
return StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator,ORDERED),
false);
}
private int batchSize;
private List<T> currentBatch;
private Iterator<T> sourceIterator;
public BatchingIterator(Iterator<T> sourceIterator, int batchSize) {
this.batchSize = batchSize;
this.sourceIterator = sourceIterator;
}
@Override
public boolean hasNext() {
prepareNextBatch();
return currentBatch!=null && !currentBatch.isEmpty();
}
@Override
public List<T> next() {
return currentBatch;
}
private void prepareNextBatch() {
currentBatch = new ArrayList<>(batchSize);
while (sourceIterator.hasNext() && currentBatch.size() < batchSize) {
currentBatch.add(sourceIterator.next());
}
}
}
Ein einfaches Beispiel für die Verwendung würde folgendermaßen aussehen:
@Test
public void getsBatches() {
BatchingIterator.batchedStreamOf(Stream.of("A","B","C","D","E","F"), 3)
.forEach(System.out::println);
}
Die obigen Drucke
[A, B, C]
[D, E, F]
Für unseren Anwendungsfall wollten wir die Stapel mischen und dann als Stream behalten - es sah so aus:
@Test
public void howScramblingCouldBeDone() {
BatchingIterator.batchedStreamOf(Stream.of("A","B","C","D","E","F"), 3)
// the lambda in the map expression sucks a bit because Collections.shuffle acts on the list, rather than returning a shuffled one
.map(list -> {
Collections.shuffle(list); return list; })
.flatMap(List::stream)
.forEach(System.out::println);
}
Dies gibt so etwas wie aus (es ist zufällig, also jedes Mal anders)
A
C
B
E
D
F
Die geheime Sauce hier ist, dass es immer einen Stream gibt, sodass Sie entweder einen Stream von Chargen bearbeiten oder mit jeder Charge etwas tun und dann flatMap
zurück zu einem Stream. Noch besser ist , alle der oben genannten nur läuft , wenn die letzte forEach
oder collect
oder andere Abschluss Ausdrücke PULL die Daten über den Strom.
Es stellt sich heraus, dass dies iterator
eine spezielle Art der Beendigung eines Streams ist und nicht dazu führt, dass der gesamte Stream ausgeführt wird und in den Speicher gelangt! Vielen Dank an die Java 8 Jungs für ein brillantes Design!