原标题:用 Java 实现 Stream 高效混排与 Spliterator
编译: 唐尤华
链接: dzone.com/articles/a-case-study-of-implementing-an-efficient-shufflin>
对 Stream 执行排序操作只要调用排序 API 就好了,要实现相反的效果(混排)却并不简单。
本文介绍了如何使用 Java Stream `Collectors` 工厂方法与自定义 `Spliterator` 对 Stream 进行 Shuffle(混排),支持 Eager 与 Lazy 两种模式。
1. Eager Shuffle Collector
Heinz [在这篇文章][1]中给出了一种解决方案:将整个 Stream 转换为 list,对 list 执行 `Collections#shuffle`,再转为 Stream。像下面这样封装成一个复合操作:
[1]:https://www.javaspecialists.eu/archive/Issue258.html
```java
publicstatic Collector> toEagerShuffledStream() {
returnCollectors.collectingAndThen(
toList(),
list-> {
Collections.shuffle( list);
returnlist.stream();
});
}
```
这种方法适用于对 Steam 中所有元素进行混排。由于会提前对集合中所有元素进行 Shuffle,如果只处理其中一部分则效果不佳,极端情况比如 Stream 只包含1个元素。
让我们来看看一个简单基准测试的运行结果:
```java
@State(Scope.Benchmark)
publicclassRandomSpliteratorBenchmark {
privateList source;
@Param({ "1", "10", "100", "1000", "10000", "10000"})
publicintlimit;
@Param({ "100000"})
publicintsize;
@Setup(Level.Iteration)
publicvoidsetUp(){
source = IntStream.range( 0, size)
.boxed()
. map(Object::toString)
.collect(Collectors.toList());
}
@Benchmark
publicList eager() {
returnsource.stream()
.collect(toEagerShuffledStream())
.limit(limit)
.collect(Collectors.toList());
}
```
```shell
(limit) Mode Cnt Score Error Units
eager 1thrpt 5467.796± 9.074ops/s
eager 10thrpt 5467.694± 17.166ops/s
eager 100thrpt 5459.765± 8.048ops/s
eager 1000thrpt 5467.934± 43.095ops/s
eager 10000thrpt 5449.471± 5.549ops/s
eager 100000thrpt 5331.111± 5.626ops/s
```
从上面的数据可以看出,尽管运行结果 Stream 中元素不断增加,运行效果还是相当不错。因此,对整个集合提前混排太浪费了,尤其是元素较少的时候得分很差。
让我们看看来有什么好办法。
2. Lazy Shuffle Collector
为了节省 CPU 资源,与其对集合中所有元素预处理,不如根据需要只处理其中一部分。
为了达到这个效果,需要自定义一个 Spliterator 对所有对元素随机遍历,然后通过 `StreamSupport.stream` 构造创建一个 Stream 对象:
```java
publicclassRandomSpliterator implements Spliterator {
// ...
publicstatic Collector> toLazyShuffledStream() {
returnCollectors.collectingAndThen(
toList(),
list-> StreamSupport.stream(
newShuffledSpliterator<>( list), false));