问题 Stream.sorted()。limit()的性能


Java Streams同时运行 sorted 和 limit 方法,分别返回流的排序版本并返回刚刚返回指定数量的流项的流。当这些操作连续应用时,例如:

stream.sorted().limit(qty).collect(Collectors.toList())

排序是按照排序方式执行的 qty 项目或是整个列表排序?换句话说,如果 qty 是固定的,这个操作是在 O(n)?文档没有单独指定这些方法的性能或相互结合使用。

我问的原因是这些操作的明显必要实现是排序然后限制,花时间 Θ(n * log(n))。但是这些操作可以一起执行 O(n * log(qty)) 并且智能流式框架可以在执行之前查看整个流以优化此特殊情况。


8365
2017-07-22 22:20


起源

整个流都已排序。 - Stephen C
这取决于这个流的特征;如果它的底层 Spliterator 报告说该流是 SORTED, 然后 sort() 是无操作的;否则,如前所述,整个流被排序,这意味着流开始生成的所有元素必须在开始排序操作之前被虹吸 - 这是唯一的逻辑 - fge
@fge ......但......考虑一下......有些算法会得到a的k个最小元素 N 元素未排序列表 O(N)。 stackoverflow.com/questions/5380568/...。应该可以实现Java 8流的算法,但不是OP尝试这样做的方式。 - Stephen C
@StephenC我不相信这样的算法适用于流,说实话;那你怎么处理 .sorted().filter(x -> true).filter(x -> true).limit()?潜在的 Stream 实现无法知道谓词实际上是无操作。 - fge
这种优化 - 例如融合排序和截断 - 在理论上是可行的,我们在设计流时确实考虑过这样的事情。但是,所需的权衡并不好;它会为狭隘的利益增加广泛的复杂性。 - Brian Goetz


答案:


让我首先指出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)

7
2017-07-22 23:02



OP我可以添加一个有效的收集器实现我建议在最后一段如果你感兴趣。 - sprinter
你可以用于教学目的,但它不是我的优先考虑。 - Solomonoff's Secret
@ Solomonoff'sSecret好的谢谢 - 我会把它留下来,因为我认为它不会真正为答案添加任何东西。 - sprinter


我的房子里有一个特别的收藏家 StreamEx 执行此操作的库: MoreCollectors.least(qty)

List<?> result = stream.collect(MoreCollectors.least(qty));

使用 内部的PriorityQueue实际上在未排序的输入上使用小数量时工作得更快。但是请注意,如果输入主要是排序的,那么 sorted().limit(qty)由于TimSort对于预分类数据的速度非常快,因此可以更快地工作。


4
2017-07-23 07:06





这是依赖于实现的,也可能取决于流管道是否可以“透视”之间的潜在操作 sorted() 和 limit()

即使您要询问OpenJDK实现,它也可能会发生变化,因为javadocs不保证运行时行为。但不,目前它没有实现k-min选择算法。

你还必须记住这一点 sorted() 除非他们已经拥有,否则不能在无限流上工作 SORTED 特性。


3
2017-07-22 22:45



除非他们已经有了 SORTED 特征和零比较器。 - Tagir Valeev