解決すべき同様の問題がありました。システムメモリよりも大きいストリームを取得し(データベース内のすべてのオブジェクトを反復処理)、順序を可能な限り最適化しました。10,000アイテムをバッファリングし、それらをランダム化することは問題ないと考えました。
ターゲットは、ストリームを取り込む関数でした。
ここで提案されているソリューションには、さまざまな選択肢があるようです。
- Java 8以外のさまざまな追加ライブラリを使用する
- ストリームではないものから始めます-ランダムアクセスリストなど
- スプリッターで簡単に分割できるストリームを持っている
私たちの本能はもともとカスタムコレクターを使用することでしたが、これはストリーミングを中止することを意味しました。上記のカスタムコレクターソリューションは非常に優れており、ほぼ使用しました。
以下は、Stream
sがエスケープハッチIterator
として使用できるを提供し、ストリームがサポートしていない追加の処理を実行できるという事実を利用して、不正を行うソリューションです。Iterator
ジャワ8の別のビット使用してストリームに変換されるStreamSupport
魔術を。
/**
* 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());
}
}
}
これを使用する簡単な例は次のようになります。
@Test
public void getsBatches() {
BatchingIterator.batchedStreamOf(Stream.of("A","B","C","D","E","F"), 3)
.forEach(System.out::println);
}
上記のプリント
[A, B, C]
[D, E, F]
私たちの使用例では、バッチをシャッフルしてからストリームとして保持したかったのですが、次のようになります。
@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);
}
これは次のようなものを出力します(ランダム化されているため、毎回異なります)
A
C
B
E
D
F
ここでの秘訣は、常にストリームが存在することです。したがって、バッチのストリームを操作するか、各バッチに対して何かを実行flatMap
してから、ストリームに戻すことができます。さらに良いことに、上記のすべてが最終forEach
またはcollect
、または他の終端式PULLストリームを介してデータを。
これは、ストリームに対するiterator
特別な種類の終了操作であり、ストリーム全体が実行されてメモリに読み込まれるわけではありません。素晴らしいデザインをしてくれたJava 8の皆さんに感謝します!