Java Streams同时运行sorted
和limit
方法,这些方法分别返回流的排序版本并返回仅返回指定数量的流项的流。当这些操作连续应用时,例如:
stream.sorted().limit(qty).collect(Collectors.toList())
是以对qty
项进行排序还是对整个列表进行排序的方式执行排序?换句话说,如果qty
已修复,此操作是否在O(n)
?文档没有单独指定这些方法的性能,也没有相互结合使用。
我问的原因是这些操作的明显必要实现是排序然后限制,花费时间Θ(n * log(n))
。但是这些操作可以在O(n * log(qty))
中一起执行,智能流式框架可以在执行之前查看整个流以优化这种特殊情况。
答案 0 :(得分:7)
首先让我首先指出Java语言规范对如何实现流的限制很少。因此,询问Java流的性能真的没有太大意义:它们在实现之间会有很大差异。
另请注意,Stream
是一个界面。您可以创建自己的类来实现Stream
,以便在sorted
上获得所需的任何性能或特殊行为。因此,即使在一个实现的上下文中,真正询问Stream
的性能也没有意义。 OpenJDK实现有许多实现Stream
接口的类。
话虽如此,如果我们看看OpenJDK实现,流的排序最终会在SortedOps
类中(参见source here),你会发现排序方法最终会返回有状态操作的扩展。例如:
private static final class OfInt extends IntPipeline.StatefulOp<Integer>
这些方法检查上游是否已经排序,在哪种情况下它们只是将它传递给下游。它们对于大小的流(即上游)也有特殊的例外情况,它们预先分配它们最终排序的数组,这将提高效率(超过它们用于未知大小流的SpinedBuffer
)。但是,只要上游尚未排序,它们就会接受所有项目,然后对它们进行排序,然后发送到下游实例的accept
方法。
因此得出的结论是,OpenJDK sorted
实现收集所有项目,然后进行排序,然后向下游发送。在某些情况下,当下游将丢弃某些元素时,这将浪费资源。对于特殊情况,您可以自由地实现自己的专用排序操作,该操作比此更有效。可能最直接的方法是实现一个Collector
,它保存流中n个最大或最小项的列表。您的操作可能看起来像:
.collect(new CollectNthLargest(4)).stream()
替换
.sorted().limit(4)
答案 1 :(得分:4)
我的StreamEx库中有一个特殊的收藏家执行此操作:MoreCollectors.least(qty)
:
List<?> result = stream.collect(MoreCollectors.least(qty));
它内部的uses PriorityQueue,在未排序的输入上使用小数量时实际上工作得更快。但是请注意,如果输入主要是排序的,那么sorted().limit(qty)
可能会更快,因为TimSort对于预分类数据的速度非常快。
答案 2 :(得分:3)
这取决于实现,也可能取决于流管道是否可以“看到”sorted()
和limit()
之间的潜在操作。
即使您要询问OpenJDK实现,它也可能会发生变化,因为javadocs不保证运行时行为。但不,目前它没有实现k-min选择算法。
您还必须记住sorted()
不适用于无限流,除非它们已具有SORTED
特征。