java - java Stream.peek() 如何影响字节码?

标签 java performance java-8 java-stream bytecode

据我所知,如果我有一个带有两个过滤器的流,它们将在字节码中与 && 组合。

例如

IntStream.range(1,10)
  .filter(i -> i % 2 == 0)
  .filter(i -> i % 3 == 0)
  .sum();

类似于 i % 2 == 0 && i % 3 == 0。

peek 会影响这个吗?

如果你在第一个文件管理器之后查看,你会得到 2468,如果你在第二个文件管理器之后查看,你只会得到 6(当然)。

但是如果你同时查看这两个地方

IntStream.range(1,10)
            .filter(integer -> integer % 2 == 0)
            .peek(i-> System.out.print(i))
            .filter(integer -> integer % 3 == 0)
            .peek(i-> System.out.print(i))
            .sum();

你得到24668。

我的假设是,这必定意味着操作因 peek 调用而以某种方式分离。类似的东西

if(i%2==0)
  peek
  if(i%3==0)

这是真的吗?如果是的话,它会影响性能吗(我认为不会)。

最佳答案

Stream API 是 ordinary Java API ,正如您所看到的那样。 It’s filter method接收任意 Predicate 实例,无论它是通过 lambda 表达式还是普通 class(或 enum 来命名所有可能性)实现的。

如果您随后调用 filter 两次,底层实现可以通过调用 Predicate.and 将它们连接到单个过滤器。但对于通过 lambda 表达式实现的谓词来说,无论是否存在都没有任何后果。

与自定义 Predicate 实现不同,自定义 Predicate 实现可以覆盖 and 方法,并在识别第二个 Predicate 实现的情况下提供优化的内容,为 lambda 表达式生成的类不会覆盖任何default 方法,而仅覆盖一个abstract 函数方法,此处为 Predicate.test,因此在本例中,调用 and 将获得 default 方法返回的内容,即一个新的 Predicate,它保存对两个源谓词的引用并将它们组合起来,就像不使用 Predicate.and 的流实现即可。

因此,这些可能的实现之间没有实质性差异,并且如果您在中间插入另一个操作(例如传递给 peekConsumer),则不会有任何差异。当然,它现在比不执行此操作时执行的操作要多,因此它会对性能产生影响,但与谓词无关。

但您普遍的误解似乎是您认为以下之间存在重大区别:

for(int i=1; i<10; i++) {
    if(i%2==0 && i%3==0)
        System.out.print(i);
}

for(int i=1; i<10; i++) {
    if(i%2==0) {
        System.out.print(i);
        if(i%3==0)
            System.out.print(i);
    }
}

看一下编译方法的字节码:

//  first variant            second variant
  0: iconst_1              0: iconst_1
  1: istore_1              1: istore_1
  2: iload_1               2: iload_1
  3: bipush        10      3: bipush        10
  5: if_icmpge     33      5: if_icmpge     40
  8: iload_1               8: iload_1
  9: iconst_2              9: iconst_2
 10: irem                 10: irem
 11: ifne          27     11: ifne          34
                          14: getstatic     #2    // Field java/lang/System.out:Ljava/io/PrintStream;
                          17: iload_1
                          18: invokevirtual #3    // Method java/io/PrintStream.print:(I)V
 14: iload_1              21: iload_1
 15: iconst_3             22: iconst_3
 16: irem                 23: irem
 17: ifne          27     24: ifne          34
 20: getstatic     #2     27: getstatic     #2    // Field java/lang/System.out:Ljava/io/PrintStream;
 23: iload_1              30: iload_1
 24: invokevirtual #3     31: invokevirtual #3    // Method java/io/PrintStream.print:(I)V
 27: iinc          1, 1   34: iinc          1, 1
 30: goto          2      37: goto          2
 33: return               40: return

正如您所看到的,打印语句的插入会导致打印语句的插入,仅此而已。或者,换句话说,&& 运算符并不是与两个嵌套 if 语句不同的神奇融合。两者在语义上和字节码中的作用完全相同。

这同样适用于 Stream API 的使用,尽管在那里,代码会更加复杂,因为条件表达式表示为 Predicate 实例,而插入的语句是 Consumer s。但在最好的情况下,HotSpot 优化器将为 Stream 变体生成与循环变体完全相同的优化 native 代码。

关于java - java Stream.peek() 如何影响字节码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36600728/

相关文章:

Java 性能 : Getting and Setting Lists

Android 应用架构 - 独立的代码层和数据库层

performance - hibernate 水化性能

lambda - 如何在 JAVA8 中使用 Lambda 将 List<T> 转换为 List<Map<K,V>>

java - 按作为输入给出的字段使用 Java 8 进行排序

java - 在 Multimap 内循环 Multimap

java - 如何从 JTextPane 中获取选择

java - JDBC 查询中的分页

java - .jrxml 到 .jasper 编译错误字段未找到异常

java - 如何锁定单个 ConcurrentHashMap 对象进行修改而不阻塞整个映射?