Nous avions un problème similaire à résoudre. Nous voulions prendre un flux qui était plus grand que la mémoire système (itérer à travers tous les objets d'une base de données) et randomiser l'ordre le mieux possible - nous avons pensé qu'il serait correct de mettre en mémoire tampon 10000 éléments et de les randomiser.
La cible était une fonction qui prenait un flux.
Parmi les solutions proposées ici, il semble y avoir une gamme d'options:
- Utilisez diverses bibliothèques supplémentaires non-java 8
- Commencez par quelque chose qui n'est pas un flux - par exemple une liste d'accès aléatoire
- Avoir un flux qui peut être divisé facilement dans un séparateur
Notre instinct était à l'origine d'utiliser un collecteur personnalisé, mais cela signifiait abandonner le streaming. La solution de collecteur personnalisé ci-dessus est très bonne et nous l'avons presque utilisée.
Voici une solution qui triche en utilisant le fait que Stream
s peut vous donner un Iterator
que vous pouvez utiliser comme trappe d'échappement pour vous permettre de faire quelque chose de plus que les flux ne prennent pas en charge. Le Iterator
est reconverti en un flux en utilisant un autre peu de StreamSupport
sorcellerie Java 8 .
/**
* 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());
}
}
}
Un exemple simple d'utilisation de ceci ressemblerait à ceci:
@Test
public void getsBatches() {
BatchingIterator.batchedStreamOf(Stream.of("A","B","C","D","E","F"), 3)
.forEach(System.out::println);
}
Les impressions ci-dessus
[A, B, C]
[D, E, F]
Pour notre cas d'utilisation, nous voulions mélanger les lots, puis les conserver sous forme de flux - cela ressemblait à ceci:
@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);
}
Cela produit quelque chose comme (c'est aléatoire, si différent à chaque fois)
A
C
B
E
D
F
La sauce secrète ici est qu'il y a toujours un flux, vous pouvez donc soit opérer sur un flux de lots, soit faire quelque chose pour chaque lot, puis flatMap
le retourner à un flux. Mieux encore, tous les passe au- dessus que la finale forEach
ou collect
ou d' autres expressions de terminaison PULL les données à travers le flux.
Il s'avère qu'il iterator
s'agit d'un type spécial d' opération de terminaison sur un flux et ne provoque pas l'exécution et la mise en mémoire de l'ensemble du flux! Merci aux gars de Java 8 pour un design brillant!