bigywater.tech
Java · 161

Spliterator:Stream 如何拆分数据源

Stream 的并行靠 Spliterator 拆数据源。不同数据源的特征位、拆分方式差在哪?拆不动会怎样?这篇把它们一个个打印出来(惰性与短路见前面 Stream 一篇)。

ORDERED SIZED SUBSIZED
ArrayList 的特征位(HashSet 是 DISTINCT SIZED)
[1024, 2048, 3072, …]
LinkedList 的 trySplit 每次拆出的元素个数
658 vs 107 ms
自定义 Spliterator 拆不动 / 可二分时的并行耗时

特征位

实测 · 本机执行输出
  ArrayList    ORDERED SIZED SUBSIZED
  LinkedList   ORDERED SIZED SUBSIZED
  HashSet      DISTINCT SIZED
  TreeSet      ORDERED DISTINCT SORTED SIZED
  ArrayDeque   ORDERED SIZED NONNULL SUBSIZED
  IntStream.range ORDERED DISTINCT SORTED SIZED NONNULL IMMUTABLE SUBSIZED
  Stream.iterate ORDERED IMMUTABLE
  filter 之后    ORDERED

trySplit

实测 · 本机执行输出
  各来源连续 trySplit 6 次,每次拆出的前半部分元素个数(estimateSize,N = 10000):
    ArrayList  [5000, 2500, 1250, 625, 312, 156]
    LinkedList [1024, 2048, 3072, 3856, -1]
    HashSet    [5000, 2500, 1250, 625, 312, 156](HashSet 的 estimateSize 是剩余元素数)
    TreeSet    [5000, 2500, 1250, 625, 312, 156]
    Stream.iterate 无限流 [4611686018427387903, 4611686018427387903, 4611686018427387903, 4611686018427387903](-2 表示大小未知 Long.MAX_VALUE)

拆不动的代价

实测 · 本机执行输出
  自定义 Spliterator,trySplit 正常二分:并行流用了 10 个线程,耗时 107 ms(sum=99870)
  自定义 Spliterator,trySplit 直接返回 null:并行流用了 1 个线程,耗时 658 ms(sum=99870)
  ArrayList 20 万个元素 parallelStream:用了 10 个线程,耗时 98 ms
  LinkedList 20 万个元素 parallelStream:用了 10 个线程,耗时 90 ms

分步演示

Spliterator
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,10 核
    • 没有验证:forEachRemaining 与 tryAdvance 的性能差异、SUBSIZED 对并行结果合并的影响、Spliterator 与 fork/join 的任务分解阈值、其它集合(ConcurrentHashMap、ArrayDeque 以外)的拆分策略、JDK 17 与 27 的差异;耗时是一次运行的结果
    京ICP备2026067502号-1京公网安备11010502063487号