我们有一个类似的问题要解决。我们想要一个大于系统内存的流(遍历数据库中的所有对象)并尽可能地随机化顺序-我们认为可以缓存10,000个项目并将它们随机化是可以的。
目标是接受流的功能。
在这里提出的解决方案中,似乎有多种选择:
- 使用各种非Java 8附加库
- 从不是流的内容开始-例如随机访问列表
- 拥有可以在拆分器中轻松拆分的流
我们的本能最初是使用自定义收集器,但是这意味着退出流式处理。上面的定制收集器解决方案非常好,我们几乎使用了它。
这是一个解决方案,它利用Stream
s可以为您提供一个Iterator
可以用作逃生舱口的事实来作弊,让您做一些流不支持的事情。Iterator
使用Java 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的出色设计!