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