当前位置: 首页 > 知识库问答 >
问题:

可以跳过Stream.peek()进行优化

韩鸿
2023-03-14

我在声纳中发现了一条规则,它说:

与其他中间流操作的一个关键区别是,为了优化目的,流实现可以跳过对< code>peek()的调用。这可能会导致< code>peek()仅针对流中的某些元素或不针对流中的任何元素被意外调用。

另外,Javadoc中提到了它,它说:

此方法主要用于支持调试,您希望在元素流经管道中的某个点时看到这些元素

这种情况下可以跳过java.util.Stream.peek()吗?和调试有关吗?

共有3个答案

束高雅
2023-03-14

该线程中引用的优化是基于惰性计算的html" target="_blank">java流的已知架构。

溪流是懒惰的;只有在终端操作启动时才对源数据执行计算,并且仅在需要时才消耗源元素。(java文档)

中间操作返回一个新的流。他们总是懒惰;执行filter()这样的中间操作实际上并不执行任何过滤,而是创建一个新的流,该流在被遍历时包含与给定谓词匹配的初始流的元素。管道源的遍历直到管道的终端操作被执行后才开始。(java文档)

这种惰性计算不仅会影响< code >,还会影响其他几个操作符。窥视。与peek(这是一个中间操作)受此延迟计算影响的方式相同,所有其他中间操作也会受到影响(< code>filter、< code>map、< code>mapToDouble、< code>mapToLong、< code>flatMap、< code>flatMapToInt、< code>flatMapToDouble、< code>flatMapToLong)。但是不理解惰性计算概念的人可能会陷入< code >的陷阱。窥视声纳报告在这里。

因此,声纳正确报告的示例

Stream.of("one", "two", "three", "four")
                .filter(e -> e.length() > 3)
                .peek(e -> System.out.println("Filtered value: " + e));

不应按原样使用,因为上面的示例中不存在终端操作。所以流根本不会调用中间的< code >。peek运算符,即使有2个元素(< code >“三个”,< code >“四个”)有资格通过流管道。

例1。添加一个终端操作符,如下所示:

Stream.of("one", "two", "three", "four")
                .filter(e -> e.length() > 3)
                .peek(e -> System.out.println("Filtered value: " + e))
                .collect(Collectors.toList());  // <----

并且传递的元素也将通过 .peek 中间运算符传递。在此示例中,永远不会跳过任何元素。

示例2.如果您使用其他终端运算符,例如<code>,那么这里是有趣的部分。findFirst,因为流Api基于延迟计算

Stream.of("one", "two", "three", "four")
                .filter(e -> e.length() > 3)
                .peek(e -> System.out.println("Filtered value: " + e))
                .findFirst();  // <----

只有1个元素会通过运算符< code >。peek而不是2。

但是只要你知道你在做什么(例1)并且你已经理解了懒惰计算,你就可以预期在某些情况下< code >。peek将被调用用于流通道中的每个元素,并且不会跳过任何元素,在其他情况下,您将知道哪些元素将从< code >中跳过。窥视。

但是,如果您将. peek与并行流一起使用,请非常小心,因为可能会出现另一组陷阱。正如. peek的java API提到的:

对于并行流管道,可以在 * 的任何时间调用操作,无论在什么线程中,* 上游操作都使元素可用。如果操作修改了共享状态,则 * 它负责提供所需的同步

陈斌蔚
2023-03-14

peek()是一个中间操作,它期望消费者对流的元素执行操作(副作用)。

如果流管道线不包含可以更改流中元素数量的中间操作,例如过滤器限制等,并以终端操作count()结束,并且当流源允许评估其中的元素数量时,则count()只需询问源并返回结果。所有中间操作都得到了优化。

注意:这种对< code>count()操作的优化从Java 9开始就存在了(参见API注释),它与< code>peek()没有直接关系,它会影响每一个不改变流中元素数量的中间操作(目前是< code>map(),< code>sorted(),< code>peek())。

< code>peek()在其他中间操作中有一个非常特殊的位置。

就其性质而言,peek()不同于其他中间操作,如map(),也不同于导致副作用的终端操作(如peek()所做的),对到达它们的每个元素执行最终操作,即forEach()forEachOrdered()

关键是peek()对流执行的结果没有贡献。它永远不会影响终端操作产生的结果,无论是值还是最终操作。

换句话说,如果我们从管道中扔掉peek(),不会影响终端操作。

方法的文档peek()以及Stream API留档警告它的操作可能被省略,您不应该依赖它。

引用<code>peek()的文档:

在流实现能够优化掉部分或全部元素的情况下(比如findFirst之类的短路操作,或者在count()中描述的示例中),不会为这些元素调用操作。

引用API文档,段落副作用:

副作用的省略也可能令人惊讶。除了终端操作foreachforEachOrded之外,当流实现可以在不影响计算结果的情况下优化行为参数的执行时,行为参数的副作用可能并不总是被执行。

下面是一个流(链接到源代码)的示例,其中除了<code>peek()</code>之外,没有任何中间操作被省略:

Stream.of(1, 2, 3)
    .parallel()
    .peek(System.out::println)
    .skip(1)
    .map(n -> n * 10)
    .forEach(System.out::println);

在此管道行peek()主持skip(),因此您可能希望它在控制台上显示来自源代码的每个元素。但是,它不会发生(元素1将不会被打印)。由于peek()的性质,它可能会在不破坏代码的情况下被优化,即不影响终端操作。

这就是为什么文档明确指出此操作专门用于调试目的,并且应该为它分配一个在任何情况下都需要执行的操作。

陈斌蔚
2023-03-14

不仅可以跳过peek,还可以跳过map。这是为了优化。例如,当调用终端操作count()时,peekmap单个项目是没有意义的,因为此类操作不会更改当前项目的数量/计数。

这里有两个例子:

1. Map和peek不会跳过,因为过滤器可以预先更改项目数量。

long count = Stream.of("a", "aa")
    .peek(s -> System.out.println("#1"))
    .filter(s -> s.length() < 2)
    .peek(s -> System.out.println("#2"))
    .map(String::length)
    .count();
#1
#2
#1
1

2. Map和peek被跳过,因为项目数量不变。

long count = Stream.of("a", "aa")
    .peek(s -> System.out.println("#1"))
    //.filter(s -> s.length() < 2)
    .peek(s -> System.out.println("#2"))
    .map(String::length)
    .count();
2

重要提示:这些方法应该没有副作用。

通常,不鼓励流操作的行为参数中的副作用,因为它们通常会导致无意中违反无状态要求,以及其他线程安全危险。

下面的实现是危险的。假设< code>callRestApi方法执行REST调用,它不会被执行,因为流违反了副作用。

long count = Stream.of("url1", "url2")
                .map(string -> callRestApi(HttpMethod.POST, string))
                .count();
/**
 * Performs a REST call
 */
public String callRestApi(HttpMethod httpMethod, String url);
 类似资料:
  • 问题内容: Java编译器(即javac)在生成字节码时不会执行任何优化。是真的吗 如果是这样,那么它可以实现为中间代码生成器以消除冗余并生成最佳代码吗? 问题答案: 如果有的话,只会做很少的优化。 关键是JIT编译器完成了大部分优化工作-如果它具有很多信息,则效果最佳,如果执行优化,其中的一些信息也可能会丢失。如果执行某种形式的循环展开,那么JIT很难以一般的方式自行完成-而且,由于它了解目标平

  • 问题内容: 我正在尝试使用php驱动程序计算mongo db集合中给定记录的跳过值。因此,获取给定的记录,找出整个集合中该记录的索引。这可能吗? 目前,我正在选择所有记录,并手动对结果数组进行索引。 问题答案: 这称为“转发分页”,这是一个概念,当您使用“排序的”结果时,可以沿“转发”方向对结果进行“有效分页”。 包含JavaScript逻辑(因为它可以在Shell中使用),但翻译起来并不难。 一

  • 问题内容: 我在jenkins中有一组构建作业,可以在3个构建节点中的任何一个中运行。它们都带有通用标签“ ubuntu_build”。每个节点都有许多执行程序,因此允许某些构建在计算机上并行执行。该安装程序运行正常,可以完成预期的工作,但我想对其进行改进。 3个构建节点具有不同的性能。第一个是第二个的两倍,第二个是第三个的两倍。(称它们为fast_node,regular_node,slow_n

  • 问题内容: 我有一个大的可迭代项,实际上,由以下给定: 我想访问百万分之一的元素。我确实以不同的方式解决了问题。 强制迭代到列表并获取第1000000个元素: 手动跳过直到999999的元素: for i in xrange(999999): p.next() return p.next() 手动跳过元素v2: for i, element in enumerate(p): if i == 999

  • 我只是向类的构造函数添加了一些语句。 这直接导致了大约10项测试失败。 而不是摆弄那些测试,我只想在Python的优化中运行pytest,并打开(开关,这意味着都被忽略)。但是看了这些文件,搜索了一下,我找不到一个方法来做到这一点。 我有点想知道这是否是一种不好的做法,因为在测试期间可能是查看s是否失败的时候。 另一方面,另一个想法是,您可能有某些测试(集成测试等)。)应该在没有优化的情况下运行,

  • GHC有很多可以执行的优化,但我不知道它们都是什么,也不知道它们在什么情况下执行的可能性有多大。 我的问题是:我可以期望它每次应用什么转换,或者几乎如此?如果我看一段经常执行(评估)的代码,我的第一个想法是“嗯,也许我应该优化它”,在这种情况下,我的第二个想法应该是“不要想它,GHC得到了这个”? 我在读《流融合:从列表到流再到什么都没有》这篇论文,他们使用的将列表处理改写成另一种形式的技术,GH