当前位置: 首页 > news >正文

深入解析 Java Stream 设计:从四幕剧看流水线设计与执行机制

java.util.stream 包是 Java Stream API 的核心,它提供了一套用于以声明式方式处理数据序列的工具。其内部实现涉及多个类和接口,它们协同工作以支持流的创建、转换和聚合操作,尤其是在并行处理方面。

Stream的一个完整流程分析:四幕剧

我们可以把整个 list.stream().filter(...).xxx().toArray() 的过程想象成一场精心编排的四幕剧。

第一幕:搭建舞台(懒加载的流水线构建)

  • list.stream(): 创建了流水线的源头 (Source Stage)。它内部封装了一个指向 list 的 Spliterator 的 Supplier(提供者)。【ReferencePipeline.Head
  • .filter(...)这步什么数据都没处理! 它只是创建了一个新的中间阶段 (Intermediate Stage),这个阶段记录了两件事:1. 它要执行的操作是“过滤”;2. 它的上游是 stream() 那个源头阶段。然后它返回自己,以便链式调用。【ReferencePipeline.StatelessOp 】
  • .xxx(): 同样,创建更多中间阶段,像链条一样一个接一个地挂在上一个阶段后面。【StatelessOp 和 ReferencePipeline. StatefulOp

此时,我们只是构建了一个执行计划,一个由 AbstractPipeline 对象组成的双向链表。就像设计好了一张工厂流水线的蓝图,但没有按下任何一个按钮,没有一个元素被处理。这就是延迟计算 (Lazy Evaluation) 的核心。

第二幕:按下开关(终端操作的触发)

  • .toArray(): 这是终端操作 (Terminal Operation)。它是整场剧的“总导演”和“启动按钮”。
  • 当调用 .toArray() 时,它内部会调用 evaluateToArrayNode(根据有状态和无状态不同处理),进而调用 evaluate 方法。这个 evaluate 方法是整个流水线的总开关。

只有终端操作才能启动数据流。没有终端操作,Stream 流水线就是一个永远不会被执行的静态结构。

第三幕:组装处理器链(Sink Chaining)

这是最精妙的一步,发生在 evaluate 方法内部。

  1. evaluate 方法会从流水线的最后一个阶段开始,反向遍历整个链条。
  2. 它对每个阶段调用 opWrapSink(flags, downstreamSink) 方法。
  3. 这个方法的作用是,把下游(后一个操作)的 Sink(处理器)作为输入,用自己这个阶段的逻辑包装一下,生成一个新的 Sink 并返回。

我们以 filter(f).map(m).toArray() 为例:

  • toArray 首先创建了一个最终的 Sink,这个 Sink 就是一个 Node.Builder(比如 FixedNodeBuilder),它的 accept 方法就是把元素加入内部数组。
  • 然后轮到 map 阶段,它调用 opWrapSink,把 Node.Builder 这个 Sink 包装成一个新的 Sink(我们称之为 MappingSink)。MappingSink 的 accept 方法会先对元素执行 m.apply(),然后把结果传给它内部包裹的 Node.Builder
  • 最后轮到 filter 阶段,它也调用 opWrapSink,把 MappingSink 包装成一个新的 Sink(我们称之为 FilteringSink)。FilteringSink 的 accept 方法会先用 f.test() 判断元素,只有通过了,才会把原始元素传给它内部包裹的 MappingSink

这个过程最终形成了一个“俄罗斯套娃”式的 Sink 链:FilteringSink(MappingSink(Node.Builder))。这个最终的、最外层的 Sink 就是即将处理所有数据的“总处理器”。

第四幕:数据流动(Spliterator 驱动)

  1. evaluate 方法(调用的子方法)拿到了最终的 Sink 链(FilteringSink)和源头的 Spliterator
  2. 它执行一个核心操作:sourceSpliterator.forEachRemaining(sinkChain)
  3. forEachRemaining 会遍历源 Spliterator 中的每一个元素,并把元素喂给 Sink 链的入口。

一个元素的旅程:

  • "apple" 从 list 中取出,被喂给 FilteringSink

  • FilteringSink.accept("apple") -> predicate.test("apple") -> true -> 调用下游 MappingSink.accept("apple")

  • MappingSink.accept("apple") -> mapper.apply("apple") -> 得到 "APPLE" -> 调用下游 Node.Builder.accept("APPLE")

  • Node.Builder.accept("APPLE") -> array[curSize++] = "APPLE"(正如你所指出的!)

  • "cat" 从 list 中取出,被喂给 FilteringSink

  • FilteringSink.accept("cat") -> predicate.test("cat") -> false -> 方法直接返回,处理链中断"cat" 永远不会到达 MappingSink 和 Node.Builder

总结

  1. 极致的性能:操作融合 (Operation Fusion) 这是对描述的流程最重要的补充。整个过程不是 filter 遍历一遍生成一个新集合,然后 map 再遍历一遍生成另一个新集合。而是,每个元素只被遍历一次,一次性地流过所有中间操作的逻辑(对于无状态操作,每个OP其实只是函数调用,没有产生中间集合)。这避免了创建任何中间集合的开销,极大地提升了性能和降低了内存占用。

  2. 单一职责原则的典范

    • Spliterator 只负责数据的分割和遍历。
    • AbstractPipeline 的各个阶段只负责构建流水线和定义 opWrapSink 的包装逻辑。
    • Sink 只负责处理单个元素并传递给下游。
    • TerminalOp 只负责启动和协调整个过程(toArray没有专门的TerminalOp,通过方法调用处理)。 每个组件的职责都非常清晰。
  3. 优雅的扩展性 这个模型可以非常自然地扩展到并行流。只需将源头的 Spliterator 通过 trySplit() 分割成多个,然后让多个线程分别用同一套 Sink 链逻辑去处理自己的数据块,最后由终端操作负责将多个线程产生的部分结果(多个 Node)合并起来即可。

Stream API 的强大之处不仅在于链式调用的简洁语法,更在于其内部通过延迟计算操作融合清晰的职责划分所带来的极致性能和优雅架构。nin FixedNodeBuilder.accept 正是这个高效数据流的最后一站。

核心接口和类

  1. BaseStream<T, S extends BaseStream<T, S>>:

    • 这是一个基础接口,定义了所有流类型(StreamIntStreamLongStreamDoubleStream)共有的基本操作,如 iterator()spliterator()isParallel()sequential()parallel()onClose()close()
    • 它是一个泛型接口,T 代表流中元素的类型,S 代表具体的流类型(例如 Stream<String> 中的 S 就是 Stream<String>)。
  2. Stream<T>:

    • 继承自 BaseStream<T, Stream<T>>
    • 定义了针对对象引用类型元素的流操作,例如 map()filter()flatMap()collect()reduce() 等。
    • 提供了一些静态方法。
  3. IntStreamLongStreamDoubleStream:

    • 分别继承自 BaseStream<Integer, IntStream>BaseStream<Long, LongStream>BaseStream<Double, DoubleStream>
    • 它们是针对原始数据类型 intlongdouble 的特化流,提供了针对这些类型的特定操作(例如 sum()average())以及避免自动装箱/拆箱的性能优势。
    • 它们也定义了各自的 mapToObj()mapToXxx() (例如 IntStream.mapToLong()) 等转换方法。
  4. Spliterator<T> (位于 java.util 包):

    • 虽然不在 java.util.stream 包下,但它是 Stream API 实现的基石。
    • Spliterator (Splitable Iterator,可分割迭代器) 用于遍历和分割源数据。它支持并行处理,因为它可以将数据源有效地分割成多个部分,供不同的线程处理。
    • Stream 的源(如集合、数组)都会提供一个 Spliterator
  5. PipelineHelper<P_OUT>:

    • 这是一个抽象类,封装了执行流管道阶段的逻辑。
    • 它知道如何将操作应用于 Spliterator,并处理顺序和并行执行的细节。
    • 每个流操作(如 mapfilter)在内部通常会对应一个 PipelineHelper 的实现,用于构建和执行流管道。
  6. AbstractPipeline<E_IN, E_OUT, S extends BaseStream<E_OUT, S>>:

    • 这是所有流管道实现类的抽象基类,它实现了 PipelineHelper 和相应的 BaseStream 接口。
    • 它管理着流的源、上游阶段 (source stage)、流的特性 (stream flags,如 ORDEREDDISTINCT) 以及并行执行的深度。
    • 具体的流实现(如 ReferencePipelineIntPipeline)都继承自这个类。
  7. ReferencePipeline<P_IN, P_OUT>:

    • AbstractPipeline 的一个具体实现,用于处理对象引用类型的流 (Stream<T>)。
    • 它有内部类(也是它的子类,由此也是stream,形成链式调用)如 Head (表示流的源头)、StatelessOp (无状态中间操作如 mapfilter) 和 StatefulOp (有状态中间操作如 sorteddistinct)。
  8. IntPipelineLongPipelineDoublePipeline:

    • 类似于 ReferencePipeline,但分别用于处理 IntStreamLongStreamDoubleStream
  9. TerminalOp<E_IN, R>:

    • 表示流管道中的终端操作。它定义了如何评估流并产生最终结果。
    • 例如,count()forEach()reduce()collect() 等操作都有对应的 TerminalOp 实现。
  10. Sink<T>:

    • 这是一个接口,代表流管道中一个阶段的消费者。它定义了接受元素 (accept())、开始 (begin()) 和结束 (end()) 信号的方法,以及是否可以取消 (cancellationRequested())。
    • 中间操作通常会创建一个 Sink 链,每个 Sink 包装下一个阶段的 Sink。当元素从源流出时,它会依次通过这个链。
    • Sink.ChainedReference<T, E_OUT> 是 Sink 的一个常见实现,用于链接处理对象引用的操作。类似地,也有针对原始类型的 Sink.ChainedInt 等。
  11. Collector<T, A, R>:

    • 用于执行可变聚合操作(如将流元素收集到 ListSet 或 Map 中,或者计算总和、平均值等)。
    • Collectors (注意末尾的 's') 是一个实用工具类,提供了大量预定义的 Collector 实现。
  12. AbstractTask<P_IN, P_OUT, R, K extends AbstractTask<P_IN, P_OUT, R, K>>:

    • 这是 Stream API 中用于并行处理的 Fork/Join 任务的抽象基类。
    • 它继承自 java.util.concurrent.CountedCompleter
    • 当一个流管道以并行模式执行时,数据源的 Spliterator 会被分割,每个分割出的部分会由一个 AbstractTask 的子类来处理。
    • AbstractTask 管理任务的分割逻辑、子任务的跟踪以及中间结果的合并。
    • 它的核心方法是 compute(),该方法决定是直接处理当前任务的数据块(如果足够小,即叶子节点任务),还是将其进一步分割成子任务。
    • 子类需要实现 doLeaf() (处理叶子节点) 和 makeChild() (创建子任务),并可能重写 onCompletion() (合并子任务结果)。
    • LEAF_TARGET 和 suggestTargetSize() 等方法用于确定任务分割的粒度。
  13. 各种 *Task 类 (例如 ForEachTaskReduceTaskFindTaskCollectTask 等):

    • 这些是 AbstractTask 的具体子类,分别对应不同的终端操作或有状态的并行操作。
    • 它们实现了特定操作在并行环境下的计算和结果合并逻辑。
  14. StreamSupport:

    • 一个实用工具类,提供了基于 Spliterator 或 Supplier 创建各种流(StreamIntStream 等)的静态方法。
    • 例如,Collection.stream() 内部就可能通过 StreamSupport.stream(collection.spliterator(), false) 来创建流。
  15. Streams:

    • 一个包级私有的实用工具类,包含了一些 Stream 实现内部使用的辅助方法和内部类,例如 ConcatSpliterator (用于 Stream.concat()) 和 StreamBuilderImpl (用于 Stream.builder())。

关系图谱(简化版)

为了更清晰地展示它们的关系,我们可以想象一个简化的依赖和继承关系:

mermaid

graph TDsubgraph java.utilSpliteratorendsubgraph java.util.concurrentForkJoinPoolCountedCompleterendsubgraph "java.util.stream (Core)"direction LRsubgraph "Stream Creation & Support"StreamSupportCollectorsStreamsendsubgraph "Stream Pipeline"BaseStreamStream["Stream / IntStream / LongStream / DoubleStream"] --> BaseStreamAbstractPipeline["AbstractPipeline (Implements BaseStream, Uses PipelineHelper)"]ReferencePipeline["Specific Pipelines (e.g., ReferencePipeline)"] --> AbstractPipelineendsubgraph "Pipeline Operations"SinkTerminalOp["TerminalOp (e.g., forEach, reduce)"]endsubgraph "Parallel Execution"AbstractTask["AbstractTask (Extends CountedCompleter)"]ForEachTask["Specific Tasks (e.g., ForEachTask)"] --> AbstractTaskendend%% --- Key Relationships --- Spliterator -- "Used by for data source" --> AbstractPipelineSpliterator -- "Used by for data source" --> StreamSupportAbstractPipeline -- "Creates/Uses for processing" --> SinkAbstractPipeline -- "Initiates" --> TerminalOpAbstractPipeline -- "For Parallel Processing" --> AbstractTaskTerminalOp -- "For Parallel Processing" --> AbstractTaskForkJoinPool -- "Executes" --> AbstractTaskCountedCompleter -- "Base for" --> AbstractTask  

解释:

  • 流的创建: 通常通过集合的 stream() 方法、Arrays.stream()Stream.of()Stream.generate()Stream.iterate() 或 StreamSupport 类来创建。这些方法最终会构造一个 *Pipeline 对象(如 ReferencePipeline),并关联一个源 Spliterator
  • 中间操作: 调用如 map()filter() 等中间操作时,会在当前的 *Pipeline 对象上附加一个新的操作阶段,形成一个 *Pipeline 链。每个阶段内部会创建一个 Sink 来处理从上一个阶段传递过来的元素,并将处理结果传递给下一个阶段的 Sink
  • 终端操作: 当调用终端操作(如 collect()forEach()reduce())时,会触发整个流管道的执行。
    • 顺序执行TerminalOp 会驱动数据从源 Spliterator 流经 Sink 链,并最终计算出结果。
    • 并行执行:
      1. 终端操作会创建一个顶层的 AbstractTask(例如 ReduceTask)。
      2. 这个顶层任务的 compute() 方法会被调用(通常在 ForkJoinPool.commonPool() 中)。
      3. compute() 方法会检查当前任务持有的 Spliterator 的大小。如果数据量较大且可以分割 (trySplit() 成功),它会创建子任务 (makeChild()),并将其中一个子任务 fork() 出去(提交给 ForkJoinPool 执行),然后当前任务递归处理另一半。
      4. 这个过程会持续进行,直到任务的数据块足够小(达到 targetSize),此时任务成为叶子节点,并调用 doLeaf() 方法处理其数据块。
      5. 当子任务完成时,onCompletion() 方法会被调用,用于合并子任务的结果。CountedCompleter 机制确保了父任务在其所有子任务完成后才继续执行 onCompletion
      6. 最终,顶层任务的结果就是整个并行流操作的结果。
  • PipelineHelper 的作用: 在整个过程中,PipelineHelper 负责协调操作的执行,无论是顺序的还是并行的。它知道如何包装 Sink,如何结合 Spliterator 进行遍历,以及如何为并行执行创建和协调 AbstractTask

关键组件及其作用

  • Spliterator: 数据的直接来源,负责提供元素和支持可能的并行分割。
  • Pipeline Operation Classes (OPs) (如 ReferencePipeline.Head*.StatelessOp):
    • 代表 Stream 中的各个操作阶段(源、中间操作)。
    • 它们是构建 Stream 管道“蓝图”的组件。
    • 它们不直接处理每个数据元素。
    • 它们的核心职责之一是在终端操作触发时,通过 opWrapSink() 方法创建并连接相应的 Sink 实例链。
  • Sink (及其子接口和实现类如 ChainedReference):
    • 实际执行数据处理的“工人”。
    • 每个 Sink 实例封装了一个特定阶段的操作逻辑(如过滤、映射、聚合)。
    • 它们通过 downstream 引用链接起来,形成数据处理链。
    • 遵循 begin() -> accept()* -> end() 的生命周期。
  • StreamOpFlag: (我们之前也提到过) 这些标志描述了流和操作的特性(如 SIZEDSORTEDDISTINCTSHORT_CIRCUIT),它们可以影响 Sink 的行为和优化。例如,max() 不是短路操作,所以 cancellationRequested() 在这个例子中不那么关键,但对于 findFirst() 这样的操作就非常重要。

流程图概览:

  1. 构建阶段 (声明式)[Spliterator] -> [Head OP] -> [Filter OP] -> [MapToInt OP]

  2. 执行阶段 (终端操作 max() 触发):

    • Sink 链构建 (由 OPs 完成, 从尾到头)maxTerminalSink <-- mapToIntProcessingSink (包装 maxTerminalSink) <-- filterProcessingSink (包装 mapToIntProcessingSink)
    • 数据流动 (由 Head OP 驱动, 从头到尾)Spliterator --元素--> filterProcessingSink --过滤后元素--> mapToIntProcessingSink --转换后int--> maxTerminalSink --> max() 结果

这个例子 清晰地展示了 Stream API 如何将操作的声明(Pipeline OPs)与操作的执行(Sinks)分离开来,从而实现了延迟执行和灵活的组合。每个 "OP" 知道如何为自己代表的操作创建一个 Sink,并将这个 Sink 连接到下游的 Sink

推荐的学习路径

阶段一:理解核心API和基本概念

  1. 用户接口 (StreamIntStreamLongStreamDoubleStream):

    • 目的: 熟悉最终用户直接使用的接口,理解各种流操作(中间操作和终端操作)的语义和用途。
    • 源码:
      • java.util.stream.Stream
      • java.util.stream.IntStream
      • java.util.stream.LongStream
      • java.util.stream.DoubleStream
      • java.util.stream.BaseStream (它们共同的父接口)
    • 关注点:
      • 每个接口定义了哪些方法?
      • 中间操作 (e.g., filtermapflatMapsorteddistinctlimitskippeek) 的特性(无状态/有状态,短路)。
      • 终端操作 (e.g., forEachtoArrayreducecollectminmaxcountanyMatchallMatchnoneMatchfindFirstfindAny) 的特性。
      • 静态工厂方法 (e.g., Stream.of()Stream.iterate()Stream.generate()Stream.concat()Stream.builder())。
  2. Spliterator 接口 (java.util.Spliterator):

    • 目的: 理解 Stream API 数据源的抽象。Spliterator 是实现惰性求值和并行处理的关键。
    • 源码java.util.Spliterator 及其针对原始类型的子接口 (Spliterator.OfIntSpliterator.OfLongSpliterator.OfDouble)。
    • 关注点:
      • tryAdvance(Consumer): 如何处理单个元素。
      • forEachRemaining(Consumer): 如何处理剩余所有元素。
      • trySplit(): 如何将数据源分割以支持并行处理。这是并行流的核心。
      • estimateSize(): 获取大小估计。
      • characteristics()Spliterator 的特性 (e.g., ORDEREDSIZEDSUBSIZEDDISTINCTSORTEDNONNULLIMMUTABLECONCURRENT) 及其对流操作优化的影响。
  3. 函数式接口 (java.util.function):

    • 目的: Stream API 大量使用函数式接口作为操作参数。
    • 源码PredicateFunctionConsumerSupplierUnaryOperatorBinaryOperator, 以及它们的原始类型特化版本。
    • 关注点: 理解它们的抽象方法和在 Stream 中的应用场景。

阶段二:深入流的创建和管道构建

  1. StreamSupport :

    • 目的: 理解如何从 Spliterator 或 Supplier<Spliterator> 创建流。这是连接数据源和流管道的桥梁,也是许多集合类(如 Collection.stream())内部创建流的起点。
    • 源码java.util.stream.StreamSupport
    • 关注点:
      • stream(Spliterator<T> spliterator, boolean parallel)
      • stream(Supplier<? extends Spliterator<T>> supplier, int characteristics, boolean parallel)
      • 以及它们为 IntStreamLongStreamDoubleStream 提供的类似方法。
      • 注意它们是如何实例化 ReferencePipeline.Head 或相应原始类型管道的 Head 类的。
  2. AbstractPipeline 和具体管道实现:

    • 目的: 理解流管道的内部表示和操作链的构建。
    • 源码:
      • java.util.stream.AbstractPipeline (所有流管道实现的基类)
      • java.util.stream.ReferencePipeline (用于 Stream<T>)
      • java.util.stream.IntPipelineLongPipelineDoublePipeline (用于原始类型流)
    • 关注点:
      • 管道阶段如何链接 (source stage, upstream stage)。
      • 流标志 (StreamOpFlag) 如何从 Spliterator 特性初始化并在管道中传播。
      • sourceSpliteratorsourceSupplier 字段。
      • opWrapSink 方法的抽象概念(虽然具体实现在子类)。
      • HeadStatelessOpStatefulOp 这些内部类是如何代表不同类型的管道阶段的。
  3. Sink 接口:

    • 目的: 理解元素如何在流管道的各个阶段之间传递和处理。
    • 源码java.util.stream.Sink 及其链式实现 (e.g., Sink.ChainedReferenceSink.ChainedInt)。
    • 关注点:
      • begin(long size): 开始处理前调用。
      • accept(T t) / accept(int i): 接受并处理单个元素。
      • end(): 所有元素处理完毕后调用。
      • cancellationRequested(): 用于支持短路操作。
      • 中间操作(如 mapfilter)通常会创建一个新的 Sink 实例,该实例包装(或称为“下游”)下一个阶段的 Sink

阶段三:理解终端操作和并行执行

  1. TerminalOp 接口:

    • 目的: 理解终端操作是如何触发流计算并产生结果的。
    • 源码java.util.stream.TerminalOp
    • 关注点:
      • evaluateSequential(PipelineHelper<T> helper, Spliterator<P_IN> spliterator)
      • evaluateParallel(PipelineHelper<T> helper, Spliterator<P_IN> spliterator)
      • 研究具体的终端操作实现类,例如:
        • java.util.stream.ForEachOps (实现了 forEachforEachOrdered)
        • java.util.stream.ReduceOps (实现了 reduce)
        • java.util.stream.MatchOps (实现了 anyMatchallMatchnoneMatch)
        • java.util.stream.FindOps (实现了 findFirstfindAny)
        • java.util.stream.CollectOps (实现了 collect)
  2. Collector 接口和 Collectors 工具类:

    • 目的: 理解可变聚合操作是如何工作的。
    • 源码:
      • java.util.stream.Collector
      • java.util.stream.Collectors
    • 关注点:
      • Collector 接口的五个方法:supplier()accumulator()combiner()finisher()characteristics()
      • Collectors 类中各种预定义收集器的实现,例如 toList()toSet()toMap()groupingBy()joining()
  3. 并行处理核心:AbstractTask 及其子类:

    • 目的: 这是 Stream 并行处理的核心,理解 Fork/Join 框架如何被用于并行化流操作。
    • 源码:
      • java.util.stream.AbstractTask (Fork/Join 任务的基类,继承自 CountedCompleter)
      • 各种具体的 Task 实现,如 ForEachTaskReduceTaskSliceTaskFindTaskCollectTask 等。
    • 关注点:
      • compute(): 任务执行的核心逻辑,决定是进一步拆分还是执行叶子节点操作。
      • makeChild(Spliterator<P_IN> spliterator): 创建子任务。
      • doLeaf(): 叶子节点任务的具体处理逻辑。
      • onCompletion(CountedCompleter<?> caller): 子任务完成时合并结果。
      • targetSize 和 LEAF_TARGET: 控制任务拆分的粒度。
      • 与 Spliterator.trySplit() 的交互。
  4. 有状态中间操作的实现:

    • 目的: 理解像 distinct()sorted()limit()skip() 这样的有状态操作是如何实现的,尤其是在并行模式下。
    • 源码:
      • java.util.stream.DistinctOps
      • java.util.stream.SortedOps
      • java.util.stream.SliceOps (用于 limit 和 skip)
    • 关注点:
      • 它们通常需要缓冲元素。
      • 并行实现可能更复杂,需要协调不同子任务的结果。例如,并行 sorted() 可能涉及样本排序或归并。

阶段四:辅助和工具类

  1. Streams (包级私有工具类):

    • 目的: 包含一些 Stream 实现内部使用的辅助方法和内部类。
    • 源码java.util.stream.Streams
    • 关注点ConcatSpliteratorStreamBuilderImplcomposedClose 等。
  2. StreamOpFlag (枚举):

    • 目的: 理解流的各种特性(如 ORDEREDDISTINCTSORTEDSIZED)是如何被编码、传播和用于优化的。
    • 源码java.util.stream.StreamOpFlag

学习建议

  • 从简单到复杂: 先理解顺序流的执行,再深入并行流。先理解无状态操作,再学习有状态操作。
  • 使用IDE: 利用 IDE 的“查找用法”、“跳转到定义/实现”、“调试”等功能,可以极大地帮助跟踪代码逻辑。
  • 调试执行: 选择一个简单的 Stream 链 (例如 List.of(1,2,3).stream().map(i -> i * 2).filter(i -> i > 3).findFirst()),然后在关键的 JDK 方法(如 StreamSupport.streamReferencePipeline.Head 构造函数, AbstractPipeline.opWrapSinkSink.acceptTerminalOp.evaluateSequentialAbstractTask.compute 等)打上断点,单步调试,观察调用栈和变量状态。
  • 阅读 Javadoc: JDK 源码中的 Javadoc 通常包含重要的设计思路和实现细节。
  • 画图: 对于复杂的流程,如 Sink 链的构建或并行任务的拆分合并,画图可以帮助理解。
  • 带着问题学习: 例如,“map 操作是如何实现的?”、“并行 reduce 是如何合并结果的?”、“distinct 在并行时如何保证正确性?”

http://www.dtcms.com/a/303101.html

相关文章:

  • 对于ui=f(state)的理解(react)
  • Redis四种GetShell方式完整教程
  • 使用Docker在Rocky Linux 9.5上在线部署LangFlow
  • 【STM32编码器接口测速】实现测速功能
  • 删除二维特征图中指定区域的样本
  • linux系统----Ansible中的playbook简单应用
  • 【Java EE】多线程-初阶-线程的状态
  • java里List链式编程
  • 4、如何生成分布式ID?
  • Linux->模拟实现 fopen/fread/fwrite
  • Bruce Momjian 深圳 meetup 回顾
  • 大模型基础设施搭建 - 操作系统centos7
  • SDRAM
  • CTF-Web学习笔记:文件包含篇
  • 阿里给AI To C战略戴上眼镜
  • 4.应用层自定义协议与序列化
  • JUC线程池: ScheduledThreadPoolExecutor详解
  • VMWARE -ESXI-ntp时间同步无法启动异常处理
  • Go-Elasticsearch Typed Client 使用命名、结构与约定
  • Java 大视界 -- Java 大数据在智能安防入侵检测系统中的多源数据融合与误报率降低策略(369)
  • AI原生应用:从人机关系重构到数字空间革命
  • 【分布式版本控制系统】Git的使用
  • 力扣17:电话号码的字母组合
  • 若依【(前后端分离版)SpringBoot+Vue3】
  • Android通知(Notification)全面解析:从基础到高级应用
  • 数据结构:下三角矩阵(Lower Triangular Matrix)
  • Eigen 中矩阵的拼接(Concatenation)与 分块(Block Access)操作使用详解和示例演示
  • 秩为1的矩阵的特征和性质
  • WireShark 抓包
  • Spring Boot项目生产环境部署完整指南