[Java] 以 IntStream 为例,浅析 Stream 的实现

IntStream 为例,浅析 JavaStream 的实现

我们在使用 Stream 时,典型的步骤是

  1. 创建 Stream
  2. 执行若干(可以是 0 个)中间操作
  3. 执行终止操作

下面举一个具体的例子。 假设现在需要计算小于 10 的所有正偶数的立方和,即

<math xmlns="http://www.w3.org/1998/Math/MathML"> 2 3 + 4 3 + 6 3 + 8 3 2^3 + 4^3 + 6^3 + 8^3 </math>23+43+63+83

可以用如下的代码来计算。⬇️

java 复制代码
import java.util.stream.IntStream;

public class SimpleIntStream {
    public static void main(String[] args) {
        // Suppose that we need to calculate the cubic sum of positive even numbers that are less than 10
        int result = IntStream.range(1, 10) // Step 1
                .filter(n -> (n & 1) == 0) // Step 2
                .map(n -> n * n * n) // Step 3
                .sum(); // Step 4
        System.out.println("The result of 2**3 + 4**3 + 6**3 + 8**3 is: " + result);
    }
}

上面的代码中和 Stream 有直接关系的代码共有 4 步,我在注释中标出来了。

  • Step 1: 创建 IntStream
  • Step 2: 调用 IntStream.filter(...) 方法,生成一个新的 IntStream
  • Step 3: 调用 IntStream.map(...) 方法,生成一个新的 IntStream
  • Step 4: 调用 IntStream.sum() 方法,得到结果。

其中 Step 2Step 3 都是中间操作。 Step 4 是终止操作。

我们来看看这 4step 的背后发生了什么。

Step 1: 创建 IntStream

分析

Step 1 就是 IntStream.range(1, 10)。 它创建了 IntStream 的一个实例。 为便于叙述,我们把这个实例称为 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1。

因为 IntStream 是接口,所以其背后必定有某个实现类。

借助 Intellij IDEA,可以看到对应的实现类是 IntPipeline.Head (全限定类名是 java.util.stream.IntPipeline.Head)⬇️

我画了一张图来表示 Step 1 背后的逻辑 ⬇️

这张图中,灰色背景的节点表示 Step 1 的代码, 蓝色背景的节点表示 IntPipeline.Head 的实例,即 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1(完整的类名太长了,在图中就简称为 Head 了)。

图中有一条名为 sourceStage 的绿色边,这个 sourceStage 字段来自 AbstractPipeline 类。 Head 类和 AbstractPipeline 和之间的关系如下图所示 ⬇️

总结

Step 1 创建了 IntPipeline.Head 类的实例 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1。

Step 2: 执行 filter 操作得到新的 IntStream

分析

Step 1 创建了 IntStream 的一个实例 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1, Step 2 通过 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1 调用 IntStream.filter(...) 方法,并生成 IntStream 的又一个实例。我们称这个新实例为 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2。

借助 Intellij IDEA,可以看到 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2 是 java.util.stream.IntPipeline.StatelessOp 的一个 子类 的实例 ⬇️ 注意: <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2 的精确类型是 java.util.stream.IntPipeline$10,由于这是一个匿名内部类,我们不必关心这个类的具体名称。

我画了一张图来表示 Step 2 背后的逻辑 ⬇️

灰色背景的节点表示 Step 2 的代码, 蓝色背景的两个节点表示 IntStream 的两个实例,即 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2。

  • 图中有名为 previousStage 的黄色边,这个 previousStage 字段来自 AbstractPipeline 类。
  • 图中有名为 nextStage 的蓝色边,这个 nextStage 字段来自 AbstractPipeline 类。

总结

Step 2 创建了 IntPipeline.StatelessOp 类的(子类的)实例 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2,而 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2 通过 previousStage 字段和 nextStage 字段组成了一个双向链表。

Step 3: 执行 map 操作得到新的 IntStream

分析

Step 2 创建了 IntStream 的实例 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2, Step 3 通过 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2 调用 IntStream.map(...) 方法,生成 IntStream 的一个实例。我们称这个新实例为 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3。

借助 Intellij IDEA,可以看到 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3 是 java.util.stream.IntPipeline.StatelessOp 的一个 子类 的实例 ⬇️

注意: <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3 的精确类型是 java.util.stream.IntPipeline$4,由于这是一个匿名内部类,我们不必关心这个类的具体名称。

下图展示了 Step 3 背后的逻辑 ⬇️

灰色背景的节点表示 Step 3 的代码, 蓝色背景的三个节点表示 IntStream 的三个实例,即 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3。

总结

Step 3 创建了 IntPipeline.StatelessOp 类的(子类的)实例 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3,而 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3 组成了一个双向链表。

  • nextStage 用于指向链表中的下一个节点。如果用 <math xmlns="http://www.w3.org/1998/Math/MathML"> → \rightarrow </math>→ 来表示 nextStage 的话,那么这三个节点的关系可以表示为 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1 <math xmlns="http://www.w3.org/1998/Math/MathML"> → \rightarrow </math>→ <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2 <math xmlns="http://www.w3.org/1998/Math/MathML"> → \rightarrow </math>→ <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3
  • previousStage 用于指向链表中的上一个节点。如果用 <math xmlns="http://www.w3.org/1998/Math/MathML"> ← \leftarrow </math>← 来表示 previousStage 的话,那么这三个节点的关系可以表示为 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1 <math xmlns="http://www.w3.org/1998/Math/MathML"> ← \leftarrow </math>← <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2 <math xmlns="http://www.w3.org/1998/Math/MathML"> ← \leftarrow </math>← <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3

我把 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3 中几个重要字段的值列在下方的表格中(其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> d e p t h depth </math>depth 字段上文没有提到,不过它的含义比较直观)。

<math xmlns="http://www.w3.org/1998/Math/MathML"> s o u r c e S t a g e sourceStage </math>sourceStage <math xmlns="http://www.w3.org/1998/Math/MathML"> n e x t S t a g e nextStage </math>nextStage <math xmlns="http://www.w3.org/1998/Math/MathML"> p r e v i o u s S t a g e previousStage </math>previousStage <math xmlns="http://www.w3.org/1998/Math/MathML"> d e p t h depth </math>depth
<math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2 <math xmlns="http://www.w3.org/1998/Math/MathML"> n u l l null </math>null <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 0 </math>0
<math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1
<math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1 <math xmlns="http://www.w3.org/1998/Math/MathML"> n u l l null </math>null <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 2 </math>2

现在我们只关心 <math xmlns="http://www.w3.org/1998/Math/MathML"> n e x t S t a g e nextStage </math>nextStage 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> p r e v i o u s S t a g e previousStage </math>previousStage 这两个字段,我把 Step 3 的图简化一下,得到如下的结果 ⬇️

Step 4: 调用 sum() 方法

Step 1Step 3 都是在创建 IntStream 的实例,而真正的计算逻辑是在 Step 4 完成的。

借助 Intellij IDEA,可以看到 sum() 方法的逻辑如下

java 复制代码
@Override
public final int sum() {
    return reduce(0, Integer::sum);
}

我们再去查看 reduce(...) 方法 ⬇️

java 复制代码
@Override
public final int reduce(int identity, IntBinaryOperator op) {
    return evaluate(ReduceOps.makeInt(identity, op));
}

evaluate(...) 方法的代码如下 ⬇️

通过打断点可以确认,我们的代码对应的 isParallel() 值为 false

所以会运行到下面这一行。

java 复制代码
terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()))

这个 evaluateSequential(...) 方法定义在 java.util.Spliterator.TerminalOp 接口中。

我们需要去 TerminalOp 接口的实现类中查看实际运行的逻辑。

TerminalOp 接口中的 evaluateSequential(...) 方法

TerminalOp 接口的实现类是在下图红框位置生成的 ⬇️

我们再去看 ReduceOps 类中的 makeInt(...) 方法的逻辑 ⬇️

看来 ReduceOps 类中的 makeInt(...) 方法会返回一个匿名内部类,且这个匿名内部类是 ReduceOp 类的子类。

这里啰嗦一句,在打断点时,可以看到这个匿名内部类的全限定类名是 java.util.stream.ReduceOps$6,不过既然是匿名内部类,那么它的名称也就不重要了。这有点像做了好事而不愿留名的人,对这样的人来说,只要好事做完了就可以了,别人不必知道自己的名字。

我画了类图来表示这几个类的继承关系 ⬇️

由于匿名内部类(即 ReduceOps$6)中没有 override evaluateSequential(...) 方法, 所以最终调用的是 ReduceOp 类里的 evaluateSequential(...) 方法。

ReduceOp 类里的 evaluateSequential(...) 方法是这样的 ⬇️

java 复制代码
@Override
public <P_IN> R evaluateSequential(PipelineHelper<T> helper,
                                   Spliterator<P_IN> spliterator) {
    return helper.wrapAndCopyInto(makeSink(), spliterator).get();
}

所以要看以下两处逻辑

  • makeSink()
  • helper.wrapAndCopyInto(...)

先看 makeSink()

makeSink() 方法

makeSink() 的字面意思是制作 sink。怎么理解呢?

Step 1Step 3,我们生成了 3IntStream 的实例。我们可以把这些实例想象成水流, 那么水流要流到哪里去呢?要流到 sink 去。我去 Google 了一下 ⬇️

看来厨房和卫生间的水槽都可以称为 sink。水从 sink 流走, stream 也从 sink 流走。

通过打断点可以看到 makeSink() 中只有以下一句代码

java 复制代码
return new ReducingSink();

这句代码返回了 ReducingSink 类的实例,我们称这个实例为 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 1 sink_1 </math>sink1。 注意:ReducingSink 类是局部内部类,它的全限定类名是 java.util.stream.ReduceOps$5ReducingSink(我们不用关心它的具体名称)。

makeSink() 我们已经看完了,更新一下任务列表 ⬇️

  • makeSink()
  • helper.wrapAndCopyInto(...)

然后再看 helper.wrapAndCopyInto(...)

helper.wrapAndCopyInto(...) 方法

wrapAndCopyInto(...) 方法定义在 PipelineHelper 这个抽象类中 ⬇️

AbstractPipeline 类中 override 了这个方法 ⬇️

java 复制代码
// 以下代码是从 AbstractPipeline.java 里 copy 过来的
@Override
final <P_IN, S extends Sink<E_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator) {
    copyInto(wrapSink(Objects.requireNonNull(sink)), spliterator);
    return sink;
}

其中的主要逻辑是 wrapSink(...)copyInto(...),我们将任务列表更新如下 ⬇️

  • makeSink()
  • helper.wrapAndCopyInto(...)
    • wrapSink(...)
    • copyInto(...)

先看 wrapSink(...),从名字来看,似乎是要对 Sink 做一些包装的工作。 它的 javadoc 如下 ⬇️ 看来确实如此

其代码如下

java 复制代码
// 以下代码是从 AbstractPipeline.java 里 copy 过来的
@Override
@SuppressWarnings("unchecked")
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
    Objects.requireNonNull(sink);

    for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
        sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
    }
    return (Sink<P_IN>) sink;
}

通过阅读代码和打断点,会发现这段代码的作用是将所有的非 Head 节点包装成 sink。 从 Step 1Step 3,我们得到了 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3。其中只有 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1 是 Head 节点。 包装后会变成这样 ⬇️

请注意

  • 3stage 节点是从 Head 的实例算起的,所以 从上到下 依次是 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 2 stage_2 </math>stage2, <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 3 stage_3 </math>stage3 (Head 实例是 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t a g e 1 stage_1 </math>stage1)
  • 3sink 节点是从 ReducingSink 的实例算起的,所以 从下到上 依次是 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 1 sink_1 </math>sink1, <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 2 sink_2 </math>sink2, <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 3 sink_3 </math>sink3(ReducingSink 实例是 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 1 sink_1 </math>sink1),这 3sink 节点通过 downstream 字段构成了一个单链表 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 3 sink_3 </math>sink3 <math xmlns="http://www.w3.org/1998/Math/MathML"> → \rightarrow </math>→ <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 2 sink_2 </math>sink2 <math xmlns="http://www.w3.org/1998/Math/MathML"> → \rightarrow </math>→ <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 1 sink_1 </math>sink1

wrapSink(...) 方法的返回值就是上图的 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 3 sink_3 </math>sink3

wrapSink(...) 方法看完了,更新后的任务列表如下 ⬇️

  • makeSink()
  • helper.wrapAndCopyInto(...)
    • wrapSink(...)
    • copyInto(...)

然后我们再去看 copyInto(...) 方法。从名称来看,这个方法的作用应该是把 stream 中的元素 copysink 里去。

这个方法的 javadoc 如下 ⬇️

打断点后,会看到我们的代码将运行到 copyInto(...) 方法里的 if 分支 ⬇️

上图中的 wrappedSink 就是上文刚刚提到的 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 3 sink_3 </math>sink3

java 复制代码
wrappedSink.begin(spliterator.getExactSizeIfKnown());
spliterator.forEachRemaining(wrappedSink);
wrappedSink.end();

看来又有新的代码要看了,更新后的任务列表如下 ⬇️

  • makeSink()
  • helper.wrapAndCopyInto(...)
    • wrapSink(...)
    • copyInto(...)
      • begin(long) method in Sink interface
      • forEachRemaining(Consumer) method in Spliterator interface
      • end() method in Sink interface

先猜测一下 ⬇️

  • begin(long) 听起来像是在真正的处理前,做准备工作的步骤
  • forEachRemaining(Consumer) 听起来像是真正干活的步骤
  • end() 听起来像是干完活后,处理善后事宜的步骤

begin(long)end() 都是 Sink 接口中定义的方法,我把这两个方法的 javadoc 复制到下方 ⬇️

java 复制代码
// 下方的代码是从 Sink.java 中 copy 的
/**
 * Resets the sink state to receive a fresh data set.  This must be called
 * before sending any data to the sink.  After calling {@link #end()},
 * you may call this method to reset the sink for another calculation.
 * @param size The exact size of the data to be pushed downstream, if
 * known or {@code -1} if unknown or infinite.
 *
 * <p>Prior to this call, the sink must be in the initial state, and after
 * this call it is in the active state.
 */
default void begin(long size) {}

/**
 * Indicates that all elements have been pushed.  If the {@code Sink} is
 * stateful, it should send any stored state downstream at this time, and
 * should clear any accumulated state (and associated resources).
 *
 * <p>Prior to this call, the sink must be in the active state, and after
 * this call it is returned to the initial state.
 */
default void end() {}

forEachRemaining(Consumer) 来自 Spliterator 接口,它的 javadoc 如下 ⬇️

java 复制代码
// 下方的代码是从 Spliterator.java 中 copy 的
/**
 * Performs the given action for each remaining element, sequentially in
 * the current thread, until all elements have been processed or the action
 * throws an exception.  If this Spliterator is {@link #ORDERED}, actions
 * are performed in encounter order.  Exceptions thrown by the action
 * are relayed to the caller.
 * <p>
 * Subsequent behavior of a spliterator is unspecified if the action throws
 * an exception.
 *
 * @implSpec
 * The default implementation repeatedly invokes {@link #tryAdvance} until
 * it returns {@code false}.  It should be overridden whenever possible.
 *
 * @param action The action
 * @throws NullPointerException if the specified action is null
 */
default void forEachRemaining(Consumer<? super T> action) {
    do { } while (tryAdvance(action));
}

但通过打断点,可以看到,我们的代码实际上执行的并不是上面这个方法,Spliterator.OfInt 这个嵌套类(其实它是个接口,但是如果叫"嵌套接口"的话,感觉有点拗口)中 overrideforEachRemaining(Consumer) 方法。我们的代码会执行这个 override 的方法 ⬇️

通过打断点,我们会发现,上图的 if 分支成立。 这个 if 分支里只有下面一行代码

java 复制代码
forEachRemaining((IntConsumer) action);

上面的这个 forEachRemaining(IntConsumer) 方法在 Spliterator.OfInt 类中。 打断点后,会发现 java.util.stream.Streams.RangeIntSpliterator 类中 overrideforEachRemaining(IntConsumer) 方法。

这里有一点绕,我们可以参考下方的类图来辅助理解。

打断点后,可以看到 i 会从 1 遍历到 10 (不包含 10)⬇️

图中的 consumer 就是上文提到的 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 3 sink_3 </math>sink3。 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 3 sink_3 </math>sink3 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 2 sink_2 </math>sink2 都是 Sink.ChainedInt 的子类的实例。

下方的表格展示了 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 3 sink_3 </math>sink3, <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 2 sink_2 </math>sink2, <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 1 sink_1 </math>sink1 是如何通过 <math xmlns="http://www.w3.org/1998/Math/MathML"> d o w n s t r e a m downstream </math>downstream 字段连接起来的 ⬇️

<math xmlns="http://www.w3.org/1998/Math/MathML"> c l a s s class </math>class <math xmlns="http://www.w3.org/1998/Math/MathML"> d o w n s t r e a m downstream </math>downstream <math xmlns="http://www.w3.org/1998/Math/MathML"> c o d e code </math>code
<math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 3 sink_3 </math>sink3 Sink.ChainedInt 的一个匿名子类 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 2 sink_2 </math>sink2
<math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 2 sink_2 </math>sink2 Sink.ChainedInt 的一个匿名子类 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 1 sink_1 </math>sink1
<math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 1 sink_1 </math>sink1 ReducingSink ⬅️ 它是一个局部内部类 没有 <math xmlns="http://www.w3.org/1998/Math/MathML"> d o w n s t r e a m downstream </math>downstream 字段

至于 begin(long)end(),它们也是沿着 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 3 → s i n k 2 → s i n k 1 sink_3 \rightarrow sink_2 \rightarrow sink_1 </math>sink3→sink2→sink1 的方向来处理的。

为了方便理解,我画了对应的表格 ⬇️

<math xmlns="http://www.w3.org/1998/Math/MathML"> c l a s s class </math>class <math xmlns="http://www.w3.org/1998/Math/MathML"> d o w n s t r e a m downstream </math>downstream code for <math xmlns="http://www.w3.org/1998/Math/MathML"> b e g i n ( l o n g ) begin(long) </math>begin(long) code for <math xmlns="http://www.w3.org/1998/Math/MathML"> e n d ( ) end() </math>end()
<math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 3 sink_3 </math>sink3 Sink.ChainedInt 的一个匿名子类 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 2 sink_2 </math>sink2
<math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 2 sink_2 </math>sink2 Sink.ChainedInt 的一个匿名子类 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 1 sink_1 </math>sink1 注意:由于匿名子类没有 override <math xmlns="http://www.w3.org/1998/Math/MathML"> b e g i n ( l o n g ) begin(long) </math>begin(long) 方法,所以这里的 <math xmlns="http://www.w3.org/1998/Math/MathML"> b e g i n ( l o n g ) begin(long) </math>begin(long) 方法来自 Sink.ChainedInt 注意:其实本图和上一行的图对应的代码在同一个位置
<math xmlns="http://www.w3.org/1998/Math/MathML"> s i n k 1 sink_1 </math>sink1 ReducingSink ⬅️ 它是一个局部内部类 没有 <math xmlns="http://www.w3.org/1998/Math/MathML"> d o w n s t r e a m downstream </math>downstream 字段

再更新一下任务列表,Step 4 我们也看完了。

  • makeSink()
  • helper.wrapAndCopyInto(...)
    • wrapSink(...)
    • copyInto(...)
      • begin(long) method in Sink interface
      • forEachRemaining(Consumer) method in Spliterator interface
      • end() method in Sink interface

参考资料

相关推荐
0wioiw04 分钟前
Python基础(Flask①)
后端·python·flask
啊阿狸不会拉杆29 分钟前
《算法导论》第 27 章 - 多线程算法
java·jvm·c++·算法·图论
用户8029735654129 分钟前
【水平:编写简单的SpringCloud】用一篇文章精通SpringCloud-1
java
风象南32 分钟前
SpringBoot 自研运行时 SQL 调用树,3 分钟定位慢 SQL!
spring boot·后端
Jenny36 分钟前
第九篇:卷积神经网络(CNN)与图像处理
后端·面试
大志说编程38 分钟前
LangChain框架入门16:智能客服系统RAG应用实战
后端·langchain·aigc
蔡俊锋41 分钟前
Javar如何用RabbitMQ订单超时处理
java·python·rabbitmq·ruby
沸腾_罗强1 小时前
Redis内存爆了
后端
天天摸鱼的java工程师1 小时前
Snowflake 雪花算法优缺点(Java老司机实战总结)
java·后端·面试
Miraitowa_cheems1 小时前
LeetCode算法日记 - Day 11: 寻找峰值、山脉数组的峰顶索引
java·算法·leetcode