gpt4 book ai didi

java - java 8 顺序流是否有任何直接或间接的性能优势?

转载 作者:行者123 更新时间:2023-12-01 22:53:15 31 4
gpt4 key购买 nike

在浏览顺序流的文章时,我想到了一个问题,即使用顺序流相对于传统的 for 循环或流是否有任何性能优势,或者流只是具有额外性能开销的顺序语法糖?

考虑以下示例,其中我看不到使用顺序流的任何性能优势:

Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s);
return s.startsWith("a");
})
.forEach(s -> System.out.println("forEach: " + s));

使用经典Java:
String[] strings = {"d2", "a2", "b1", "b3", "c"};
for (String s : strings)
{
System.out.println("Before filtering: " + s);
if (s.startsWith("a"))
{
System.out.println("After Filtering: " + s);
}
}

点这里是 a2 的流处理仅在 d2 上的所有操作完成后才开始(之前我认为当 d2 由 foreach 处理时,filter 会在 a2 上进行操作,但根据本文,情况并非如此: https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/) ,经典 java 的情况也是如此,那么使用流超出“表现力”和“优雅”编码风格的动机应该是什么?我知道在处理流时编译器有性能开销,有没有人知道/经历过任何性能使用顺序流的好处?

最佳答案

首先,让特殊情况,比如省略多余的sorted操作或在 count() 上返回已知大小,除此之外,操作的时间复杂度通常不会改变,因此执行时间的所有差异通常都是关于恒定偏移量或(相当小的)因素,而不是根本性的变化。

您始终可以编写一个手动循环,其操作与 Stream 实现在内部所做的基本相同。所以,内部优化,正如 this answer 提到的总是会因为“但我可以在我的循环中做同样的事情”而被解雇。
但是……当我们将“流”与“循环”进行比较时,假设所有手动循环都是针对特定用例以最有效的方式编写的,真的合理吗?无论调用代码作者的经验水平如何,特定的 Stream 实现都会在适用的情况下将其优化应用于所有用例。我已经看到循环错过了短路或执行特定用例不需要的冗余操作的机会。
另一方面是执行某些优化所需的信息。 Stream API 是围绕 Spliterator 构建的可以提供源数据特征的接口(interface),例如它允许找出数据是否具有需要为某些操作保留的有意义的顺序,或者它是否已经预先排序、自然顺序或使用特定比较器。当可预测时,它还可以提供预期的元素数量,作为估计值或精确值。
接收任意 Collection 的方法, 用一个普通的循环来实现一个算法,很难发现,是否有这样的特性。一个 List意味着一个有意义的顺序,而 Set通常不会,除非它是 SortedSetLinkedHashSet ,而后者是一个特定的实现类,而不是一个接口(interface)。因此,针对所有已知星座的测试可能仍然会错过具有无法通过预定义接口(interface)表达的特殊契约(Contract)的第 3 方实现。
当然,从 Java 8 开始,您可以获得 Spliterator您自己来检查这些特征,但这会将您的循环解决方案更改为一件重要的事情,并且还意味着重复使用 Stream API 已经完成的工作。
Spliterator 之间还有另一个有趣的区别。基于 Stream 解决方案和传统循环,使用 Iterator当迭代数组以外的东西时。模式是调用hasNext在迭代器上,后跟 next , 除非 hasNext返回 false .但是Iterator的契约(Contract)不强制要求这种模式。调用者可以调用 next没有 hasNext ,甚至多次,当它已知成功时(例如,您确实已经知道集合的大小)。此外,调用者可能会调用 hasNext多次没有next以防调用者不记得上次调用的结果。
因此,Iterator实现必须执行冗余操作,例如循环条件被有效检查两次,一次在 hasNext , 返回 boolean ,并且一次在 next , 抛出 NoSuchElementException未完成时。通常,hasNext必须执行实际的遍历操作并将结果存储到 Iterator例如,确保结果在后续 next 之前一直有效。称呼。 next反过来,操作必须检查这样的遍历是否已经发生,或者它是否必须自己执行操作。实际上,热点优化器可能会也可能不会消除 Iterator 带来的开销。设计。
相比之下,Spliterator有一个遍历方法,boolean tryAdvance(Consumer<? super T> action) ,它执行实际操作并返回是否存在元素。这显着简化了循环逻辑。甚至还有 void forEachRemaining(Consumer<? super T> action)对于非短路操作,它允许实际实现提供整个循环逻辑。例如,在 ArrayList 的情况下该操作将以对索引的简单计数循环结束,执行普通数组访问。
您可以将此类设计与例如readLine()BufferedReader ,执行操作并返回 null在最后一个元素之后,或 find()正则表达式 Matcher ,执行搜索,更新匹配器的状态并返回成功状态。
但是,在具有专门设计用于识别和消除冗余操作的优化器的环境中,很难预测这种设计差异的影响。结论是,基于 Stream 的解决方案有可能变得更快,而它是否会在特定场景中实现取决于很多因素。正如开头所说,它通常不会改变整体时间复杂度,这更值得担心。

关于java - java 8 顺序流是否有任何直接或间接的性能优势?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54238552/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com