bigywater.tech
Java · 152

Stream:惰性求值、流水线和短路操作

Stream 的惰性求值、流水线和短路到底是怎么回事?这篇用 peek 和计数器把每个元素经过了哪些阶段打印出来(惰性的 count 行为、parallelStream 的线程数在已有文章里,Spliterator 在后面)。

21 / 100 万
iterate 无限流 limit(3) 上游只产生 21 个元素,findFirst 只产生 101 个
5 vs 10
limit(5) 放在 filter 前 / 后时上游产生的元素数
0 次
大小已知的流上 count() 之前的 peek 被执行的次数

逐元素执行

实测 · 本机执行输出
  只建好流水线(没有终结操作)之后日志长度 = 0(什么都没执行)
  filter 偶数 → map ×10 → limit(2) → forEach,元素 1..6:
    filter(1) filter(2) map(2) →forEach(20) filter(3) filter(4) map(4) →forEach(40)

sorted 屏障

实测 · 本机执行输出
  中间有 sorted():所有元素先过完上游,才开始下游:
    up(5) up(3) up(8) up(1) down(1) down(3) down(5) down(8)
  没有 sorted():每个元素依次走完整条流水线:
    up(5) down(5) up(3) down(3) up(8) down(8) up(1) down(1)

短路

实测 · 本机执行输出
  无限流 iterate → filter(7 的倍数) → limit(3) → sum = 42,上游一共产生了 21 个元素
  一百万个元素的流 findFirst(x > 100) = 101,上游只产生了 101 个元素
  limit(5) 在 filter 之前:上游产生了 5 个;limit(5) 在 filter(偶数)之后:上游产生了 10 个(要凑够 5 个偶数)
  anyMatch(x == 2) = true,检查了 2 个元素
  allMatch(x < 3) = false,检查了 3 个元素(遇到第一个不满足的就停)

count() 与重复消费

实测 · 本机执行输出
  list.stream().peek(...).count() = 4;peek 执行了吗:没有(流的大小已知,count 直接用大小)
  加了 filter 之后 count() = 4;peek 执行了吗:执行了 4 次
  只有 map 时 count() = 4;peek 执行了吗:没有
  flatMap 之后 count() = 8;peek 执行了吗:执行了
  同一个流消费第二次 → IllegalStateException: stream has already been operated upon or closed

其它 JDK 版本

实测 · 本机执行输出
  JDK 17:
    list.stream().peek(...).count() = 4;peek 执行了吗:没有(流的大小已知,count 直接用大小)
    加了 filter 之后 count() = 4;peek 执行了吗:执行了 4 次
    只有 map 时 count() = 4;peek 执行了吗:没有
    flatMap 之后 count() = 8;peek 执行了吗:执行了
  JDK 27:
    list.stream().peek(...).count() = 4;peek 执行了吗:没有(流的大小已知,count 直接用大小)
    加了 filter 之后 count() = 4;peek 执行了吗:执行了 4 次
    只有 map 时 count() = 4;peek 执行了吗:没有
    flatMap 之后 count() = 8;peek 执行了吗:执行了

分步演示

Stream 流水线
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;第四节在 17.0.20 与 27 上重复
    • 没有验证:并行流下的执行顺序与短路、有状态操作 distinct/skip 的行为、Collectors 的内部、Stream 的性能(与 for 循环的比较)、flatMap 的惰性(JDK 10 前的缓冲问题);计数全部来自 peek,peek 本身是调试用途
    京ICP备2026067502号-1京公网安备11010502063487号