Stream流转原理

from 曾经对面的同事
每个Stream包含三个阶段 源、零个或多个中间操作、终止操作
源阶段

首先看一个例子,这里的例子非常简单,就是将List构造成了一个Stream,然后进行循环,
但是它是如何就将list构造成了Stream

ListstringList = new ArrayList<>();
stringList.add("颜智慧");
stringList.add("菜穗子");
stringList.stream().forEach(System.out::println);

点进stream()方法

这是JDK1.8在Collection中新增加的默认方法,用于将集合构造成Strem对象,方法大体文档注释如下,构造关键在于spliterator()方法

/**
 * 返回一个以此集合为源的流对象
 * 当这个方法无法返回IMMUTABLE和CONCURRENT特性值时应当被重写
 * 默认会以集合的顺序创建流对象
 * 默认串行
 */
    default Stream stream() {
        return StreamSupport.stream(spliterator(), false);
    }

spliterator()方法也是Collection中新增加的默认方法,它重写了Iterable中的默认实现,创建了一个Spliterator对象,E代表集合中元素的类型

/**
 * 以当前集合为源创建Spliterator对象
 * Spliterator对象应当包含某些特性值,但是如果集合为空时就不需要包含SIZED特性值
 * 这个方法应该被子类重写,应该具有不可变或是并发修改、延迟绑定、快速失败的特征,为了保证这个流与Collection中元素是等价的
 * 空的spliterator()只会包含SIZED和SUBSIZED特性值
 */
@Override
default Spliterator spliterator() {
    return Spliterators.spliterator(this, 0);
}

可以看出Spliterator非常重要,它用于collection流源的构建,其实其它的Stream构造也是利用了Spliterator接口,我们来详细通读一遍Spliterator接口的文档和方法说明;

Spliterator 分割迭代器

下面是Spliterator接口中的接口和一些特性值,方法描述我以注释的形式标识出来

package com.tinny.jdk8.spliterator;

import java.util.Comparator;
import java.util.function.Consumer;

/**
 * 名词解释:
 * Spliterator 以下简称Sp 
 * structural interference 译为:结构被修改,也就是数据源被修改
 * 

* Spliterator文档解析描述 *

* 它是一个用于分区和遍历源数据的对象,源可以是数组、集合或者是io函数; *

* 可以通过tryAdvance单独遍历或是forEachRemaining循环遍历; *

* 可以将一个Sp分裂成两个平行的Sp,用于并行计算,如果分裂代价过大或者分裂没有意义对并行来说就是负担, * 每个Sp只会作用到属于自己的那一块区域; *

* 它有一些特性值,例如ORDERED、DISTINCT等,用于简化计算,类似Collectors中特性值含义, * 例如来自List的Sp就会包含ORDERED,Set就会包含DISTINCT等; *

* 某些特性值会有特殊含义,例如ORDERED就代表会被顺序执行,JDK告诉我们不要去定义新的特性,老老实实用它现在的就行了; *

*

* 如果一个Sp不包含IMMUTABLE或者CONCURRENT,会在绑定之后进行源数据的检测, * 一个延迟绑定的Sp绑定数据源出现方法的第一次遍历、分割或是第一次查询大小,而不是第一次创建, * 一个非延迟绑定的Sp,绑定数据在于构造或是任意方法第一次调用, * 如果绑定之前修改的数据源,那么绑定后的数据也会被改变,但是如果绑定之后检测到数据结构被修改,就会抛出ConcurrentModificationException异常, * Sp有快速失效策略,例如forEachRemaining方法,Sp会优化遍历策略,并不是在每次调用元素时就会检测一次,这样效率比较低,它是在所有元素执行完毕之后再进行检测; *

* Sp提供一个估值的操作,如果包含SIZED特性,这个估值就是代表所有遇到的元素,如果不包含也会提供一个大概的值用于分割; *

* 虽然Sp分裂非常适合于并行操作,但是它不是线程安全的,需要由调用者来保证,通常都是使用顺序调用的方式来避免线程安全问题,Sp分裂后可以进行再次分裂, * 分裂最好都是在元素被消费之前进行分裂,因为元素被消费后,分裂出对应的SIZED往往是不准确的; *

* 为了提高效率,减少装箱拆箱的损耗,提供了OfInt、OfLong、OfDouble的特例,具体细节可以看代码; *

* Sp类似于Iterator,用来进行元素遍历,它提供了效率更高的并行分割遍历的方式,抛弃了Iterator的hasNext()和next()方法,避免线程竞争, * Iterator会通过两次校对hasNext()和next()获取下一个元素,Sp将其简化为一个方法,具体哪个方法我也没细看。。TODO Sp的简化遍历的方法; *

* 对于可变源,如果在源绑定到消费这个过程中,这个过程中源结构如果发生变化(替换、新增、删除),结果就会具备不确定性; *

* 可变源可以通过如下几种方式来控制 * 1、源结构不能被修改 CopyOnWriteArrayList是一个不可变的源,通过复制重写的方式来实现源结构更新,通过这种方式避免了线程竞争的方式,适合于读多写少的场景; * 2、可以并发修改的源 ConcurrentHashMap通过分段锁来控制每一片bucket; * 3、延时绑定和快速失败的源 ArrayList以及大部分的Collection的子类都具备这个特性,会在源绑定之后元素消费之前如果检测到结构被修改,会遵循快速失效的策略; * 4、非延时绑定和快速失败的源 类似3,但是由于它绑定时机更早,所有从元素绑定到检测时间会更长 * 5、延迟绑定和非快速失败的源 在绑定后进行遍历(消费)中,如果元素发生的变化,由于没有快速失败策略,后续的行为是不确定的; * 6、非延时绑定和非快速失败的源 在构造或是某个方法调用时就会被绑定,中间元素改变后,后续的行为也是不确定的;5,6两种都是有风险的 *

* 下面有两个例子介绍了Sp和并行分割基本用法,细节可以自己去看 */ public interface Spliterator { /** * 尝试获取元素 * 如果元素存在,就会执行给定操作,并返回true;元素不存在,就会返回false; * 异常会返回给调用者 * * @param action 给定动作 */ boolean tryAdvance(Consumer action); /** * 对于每个遇到的元素会在当前线程执行给定的动作,也就是循环; * 在所有元素消费完或是抛出异常后停止; * * @param action 给定动作 */ default void forEachRemaining(Consumer action) { do {} while (tryAdvance(action)); } /** * 尝试分割 * 如果当前Sp可以被分割,当前的Sp会被截取,返回一个全新的sp并持有一定数量的元素; * 如果当前Sp包含ORDERED,返回的元素也必须包含; * 除非当前Sp是一个无限流,否则重复调用trySplit()最终会返回一个null; * 拆分前的estimateSize,必须大于或是等于拆分后Sp的estimateSize; * 如果这个sp包含SUBSIZED,那么分裂前的estimateSize必须等于分裂后estimateSize的总和; * 可能会因为任意原因返回null,本身就是一个空值、已经是最小分割单元等等原因,提醒我们要做校验; * * @return New Sp/null * @apiNote 理想情况是将Sp分割成两半, 从而实现平衡计算,但是并不是说只有理想情况下效率才会最高, * 例如某些平衡树,就不适合对叶节点分割 */ SpliteratorApi trySplit(); /** * 返回一个估计元素长度值,如果是个无限流就会返回Long的MAX_VALUE; * 如果Sp包含SIZED属性,并且没有被消费、遍历和拆分,那其实就是准确的; * 即时这个值不准确也很有用,记就完事了; */ long estimateSize(); /** * 特性值 */ int characteristics(); default Comparator getComparator() { throw new IllegalStateException(); } /** * 顺序的 * 当前Sp会保证trySplit、tryAdvance、forEachRemaining中进行消费的元素都是顺序执行的; */ public static final int ORDERED = 0x00000010; /** * 不重复的 * (x,y)-> !x.equals(y) */ public static final int DISTINCT = 0x00000001; /** * 排序的 * 默认是自然顺序,可以通过getComparator进行重新定义 */ public static final int SORTED = 0x00000004; /** * 已知大小 * 标识元素在绑定之后,遍历或是消费的元素数量是有限大小; * 在没有并发修改源的情况,这个大小就是准确源大小; */ public static final int SIZED = 0x00000040; /** * 非空 */ public static final int NONNULL = 0x00000100; /** * 不可变,在元素绑定之后,源不能发生变化,否则会抛出ConcurrentModificationException */ public static final int IMMUTABLE = 0x00000400; /** * 并发,意味着源可以进行并发修改、更换和删除的操作,标识元素的SIZED不应在顶级Sp中被体现,因为源随时可能会发生变化 */ public static final int CONCURRENT = 0x00001000; /** * 子集大小,由trySplit生成的都会包含SIZED和SUBSIZED特性 */ public static final int SUBSIZED = 0x00004000; }

花费了大量篇幅介绍了分割迭代器接口,总结一下分割迭代器的用途

1.元素遍历 //TODO

然后回到Collection.spliterator()方法,跟进方法里边

    /**
     * 用给定的集合的iterator创建一个分割迭代器,使用集合的大小作为源大小,包含快速失败和延迟绑定的特性
     */
    public static  Spliterator spliterator(Collection c,
                                                 int characteristics) {
        return new IteratorSpliterator<>(Objects.requireNonNull(c),
                                         characteristics);
    }

跟进IteratorSpliterator类,这是一个Spliterators中的静态内部类,实现了Spliterator接口,利用给定集合迭代器创建一个分割迭代器,它会持有一个集合的引用。

        public IteratorSpliterator(Collection collection, int characteristics) {
            this.collection = collection;
            this.it = null;
            this.characteristics = (characteristics & Spliterator.CONCURRENT) == 0
                                   ? characteristics | Spliterator.SIZED | Spliterator.SUBSIZED
                                   : characteristics;
        }

走完上述的构造方法,一个分割迭代器已经被构造出来,然后我们往回退,回到Stream的stream()方法,它是利用了StreamSupport.stream()方法,接收了我们构造出来的Spliterator对象,跟进StreamSupport.stream()方法;

    //Collection.stream
    default Stream stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    
    //StreamSupport.stream
    /**
     *根据spliterator创建一个并行或是串行的stream
     *这个spliterator仅用于终止操作开始后的分割、转换和循环遍历;
     *强烈建议spliterator包含IMMUTABLE、CONCURRENT或是延迟绑定的特性,减少操作开始后的不可预估风险
     */
    public static  Stream stream(Spliterator spliterator, boolean parallel) {
        Objects.requireNonNull(spliterator);
        return new ReferencePipeline.Head<>(spliterator,
                                            StreamOpFlag.fromCharacteristics(spliterator),
                                            parallel);
    }

new ReferencePipeline.Head<>()会构造一个Head对象,Head继承了ReferencePipeline,ReferencePipeline是中间管道或是源管道的一个抽象基类,它继承了AbstractPipeline,并实现了Stream接口的特性,Head的构造方法会一直调用父类的构造方法,直到AbstractPipeline的构造方法,我们重点来看这个类

     /**
     * 构造一个流的源头阶段对象
     */
    Head(Spliterator source,
         int sourceFlags, boolean parallel) {
        super(source, sourceFlags, parallel);
    }

/**
 * 这是一个流管道的抽象基类,它是流的核心实现,用来创建和管理流管道
 * AbstractPipeline标示流的初始阶段,包含源和0个或多个中间操作,每个AbstractPipeline都代表一个阶段
 * 它有一些特定实现,例如IntPipeline
 * 在链接了新的中间操作后或是有一个终止操作,认为该流已经被消费,当前实例不能再做其他中间操作或是终止操作,流只能使用一次
 * 例如:
 * Stream s1 = list.stream();
 * Stream s2 = s1.filter();
 * Stream s3 = s1.map(e->e);//这种行为被称为中间操作被链接,只能链接一次,第二次就会报错
 * @apiNote 流是惰性的,没有终止操作,源数据不会被消耗
 */
 abstract class AbstractPipeline>
        extends PipelineHelper implements BaseStream {
        
        /**
     * 流的源,如果是源阶段,则为它本身
     */
    private final AbstractPipeline sourceStage;

    /**
     * 流的上游,如果为源阶段,则为空
     */
    private final AbstractPipeline previousStage;

    /**
     * 中间操作标志
     */
    protected final int sourceOrOpFlags;

    /**
     *流的下一个阶段,如果为最后一个阶段,则为空
     */
    private AbstractPipeline nextStage;

    /**
     *中间操作的次数
     */
    private int depth;

    /**
     * The combined source and operation flags for the source and all operations
     * up to and including the operation represented by this pipeline object.
     * Valid at the point of pipeline preparation for evaluation.
     */
    private int combinedFlags;

    /**
     * 源头分割迭代器,仅在头部管道有效
     * 如果它不为空,则sourceSupplier必须为空
     * 在流被消费后,如果它不会空就置为空
     */
    private Spliterator sourceSpliterator;

    /**
     * 源头分割迭代器,仅在头部管道有效
     * 如果它不为空,则sourceSpliterator必须为空
     * 在流被消费后,如果它不会空就置为空
     */
    private Supplier> sourceSupplier;

    /**
     * 如果已经被连接,或者已经被消费,置为true
     */
    private boolean linkedOrConsumed;

    /**
     * True if there are any stateful ops in the pipeline; only valid for the
     * source stage.
     */
    private boolean sourceAnyStateful;

    private Runnable sourceCloseAction;

    /**
     * 是否并行
     */
    private boolean parallel;    
        
}

这里我们调用的构造方法,走完这个方法后,一个Stream就被构造出来了

  /**
     * Constructor for the head of a stream pipeline.
     *
     * @param source {@code Spliterator} describing the stream source
     * @param sourceFlags the source flags for the stream source, described in
     * {@link StreamOpFlag}
     * @param parallel {@code true} if the pipeline is parallel
     */
    AbstractPipeline(Spliterator source,
                     int sourceFlags, boolean parallel) {
        this.previousStage = null;
        this.sourceSpliterator = source;
        this.sourceStage = this;
        this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;
        // The following is an optimization of:
        // StreamOpFlag.combineOpFlags(sourceOrOpFlags, StreamOpFlag.INITIAL_OPS_VALUE);
        this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
        this.depth = 0;
        this.parallel = parallel;
    }

一个Stream源阶段的构造就是持有了一个list或是数组的引用,通过是否被消费、流的上游、流的下游和流本身,将这个流以一种双向链表的形式串联起来;流的核心关键在于流的构建;


中间操作

理解的流的源的构造理解中间阶段操作就非常简单了,还是用上面的例子

    ListstringList = new ArrayList<>();
    stringList.add("颜智慧");
    stringList.add("菜穗子");
    stringList.stream().map(e->e).filter(e->e.equals("颜智慧")).forEach(System.out::println);

我们首先跟进map方法中,然后点进它的唯一实现,它位于java.util.stream.ReferencePipeline中,通读一下方法实现,它会返回一个Stream流,会new出一个StatelessOp的匿名实现类;

    
    @Override
    @SuppressWarnings("unchecked")
    public final  Stream map(Function mapper) {
        Objects.requireNonNull(mapper);
        /**
         * StatelessOp的匿名实现类,将中间操作连接起来构成一个新的流
         * @param this 流的上游 拿上边例子来说filter的上游就是map
         */
        return new StatelessOp(this, StreamShape.REFERENCE,
                                     StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
            /**
             * java.util.stream.AbstractPipeline 的实现 跟进Sink
             */
            @Override
            Sink opWrapSink(int flags, Sink sink) {
                return new Sink.ChainedReference(sink) {
                    @Override
                    public void accept(P_OUT u) {
                        downstream.accept(mapper.apply(u));
                    }
                };
            }
        };
    }

StatelessOp继承了ReferencePipeline、AbstractPipeline和实现了Stream,它最终会调用AbstractPipeline的构造方法,我们跟进这个构造方法,它本身就描述了流和流之间的一个双向连接指向关系,通过这种双向链表的形式来完成流的串联,而且流只能被链接一次;
返回map实现

    /**
     * 将中间操作追加到现阶段流上的一个构造函数
     *
     * @param previousStage 流的上游 upStream
     * @param opFlags 某些参数,不懂什么意思
     * {@link StreamOpFlag}
     */
    AbstractPipeline(AbstractPipeline previousStage, int opFlags) {
        if (previousStage.linkedOrConsumed)
            throw new IllegalStateException(MSG_STREAM_LINKED);
        //是否被链接
        previousStage.linkedOrConsumed = true;
        //前一阶段的下一阶段指向当前对象
        previousStage.nextStage = this;
        //前一阶段指向upStream
        this.previousStage = previousStage;
        this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;
        this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);
        //流源
        this.sourceStage = previousStage.sourceStage;
        if (opIsStateful())
            sourceStage.sourceAnyStateful = true;
        //流的操作深度 一个中间操作对应一个操作深度
        this.depth = previousStage.depth + 1;
    }
/**
 * 它是Consumer的一个扩展,用于在流管道的各个阶段来处理值,提供了额外的方法来管理大小等
 * 在调用accept方法之前,必须调用begin方法,调用结束后调用end方法,在调用完end方法后就不能再调用accept方法,除非你再次调用begin方法,也就是说Sink可以重用;
 * Sink有初始状态和激活状态,begin会转换为激活状态,end之后会变成初始状态,accept必须是Sink在激活状态
 * Sink操作流程 begin->accept->end->begin->accept->end....
 * {@code int longestStringLengthStartingWithA
 *         = strings.stream()
 *                  .filter(s -> s.startsWith("A"))
 *                  .mapToInt(String::length)
 *                  .max();
 * }
 * 上述例子表述了一个过滤、映射和汇聚的操作,每个操作代表一个Sink,每个Sink代表一个阶段,上游会发送数据到下游
 */
interface Sink extends Consumer {
    
    default void begin(long size) {}

    default void end() {}
    
     /**
      * 连接操作的抽象类,使用这个类来完成流和流之间的串联
      * 
      */
    static abstract class ChainedReference implements Sink {
        protected final Sink downstream;

        public ChainedReference(Sink downstream) {
            this.downstream = Objects.requireNonNull(downstream);
        }

        @Override
        public void begin(long size) {
            downstream.begin(size);
        }

        @Override
        public void end() {
            downstream.end();
        }

        @Override
        public boolean cancellationRequested() {
            return downstream.cancellationRequested();
        }
    }
}

中间操作用到的关键类就是这些,所有的中间操作都是这个流程,但是中间操作只有在终止操作时才会被真正调用,通过中间操作可以看出流操作两大特性

  1. 惰性操作
  2. 短路操作

大体流程如下,拿map操作来说

   
    public final  Stream map(Function mapper) {
        Objects.requireNonNull(mapper);
       /**
       * 1.构建一个中间连接操作,传入当前流(upstream)和一些特性值,这里会在构造时被调用,
       * 就比如我们构造了一个类,但是构造了不代表它里面的方法都会被触发,这就是流的惰性调用原理
       */
        return new StatelessOp(this, StreamShape.REFERENCE,
                                     StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
            /**
             *2.它会传入一个Sink作为返回的类型,这个sink就是downstream,它代表了下游收集器的类型,
             * 这里是map,它会根据你给定的动作,将输入值映射为返回值,如果是filter就会执行过滤操作,
             * 返回值会被添加到下游收集器中,作为下一步操作的数据提供;
             * 通过这里我们就能得出为什么stream调用是短路操作,比如这个map的上游是filter,如果这个数据不符合filter过滤条件,就会被排除掉,所以作为下游的map也不会执行;
             */
            @Override
            Sink opWrapSink(int flags, Sink sink) {
                return new Sink.ChainedReference(sink) {
                    @Override
                    public void accept(P_OUT u) {
                        downstream.accept(mapper.apply(u));
                    }
                };
            }
        };
    }

Stream的中间操作主要就是将流进行双向串联,将上游的数据输出到下游,如此循环往复,直到数据被消费完毕;


终止操作

流最终被调用是通过一个终止操作,还是上边的例子

    ListstringList = new ArrayList<>();
    stringList.add("颜智慧");
    stringList.add("菜穗子");
    stringList.stream().map(e->e).filter(e->e.equals("颜智慧")).forEach(System.out::println);

forEach是一个终止操作,从这里会进行中间操作的串联和中间操作的执行;

流的终止操作分为4种,我就用过以下两种,以forEach为例进行分析

  1. reduce //例如:max、min等
  2. evaluate //例如:foreach、collect等

跟进forEach方法,有两个实现方法

  1. Head的实现,也就是源直接调用forEach,用法很简单
  2. ReferencePipeline的实现,其他的调用,重点关注这个

跟进ReferencePipeline的实现,它标注了为一个终止操作(Terminal operations)

   // Terminal operations from Stream
    @Override
    public void forEach(Consumer action) {
        evaluate(ForEachOps.makeRef(action, false));
    }

首先看ForEachOps.makeRef(action, false)方法,它构造了一个TerminalOp的对象

    public static  TerminalOp makeRef(Consumer action,
                                                  boolean ordered) {
        Objects.requireNonNull(action);
        return new ForEachOp.OfRef<>(action, ordered);
    }

我们首先简单分析一下TerminalOp这个对象,它代表终止操作,所有终止操作都会实现它;

/**
 * 这是流管道中的一个操作,它会输出一个结果或者对源中数据结构* 进行改变,例如某些映射操作,就会改变源结构;
 * 它包含了一个输入类型和一个输出类型;
 * 它支持串行和并行两种实现方式;
 *
 * @param  输入元素类型
 * @param     输出结果类型
 * @since 1.8
 */
interface TerminalOp {
    
    /**
     *并行操作,默认串行
     */
    default  R evaluateParallel(PipelineHelper helper,Spliterator spliterator) {
        if (Tripwire.ENABLED)Tripwire.trip(getClass(), "{0} triggering TerminalOp.evaluateParallel serial default");
        return evaluateSequential(helper, spliterator);
    } 
    /**
     *串行操作
     */
     R evaluateSequential(PipelineHelper helper,Spliterator spliterator);
}

ForEachOp.OfRef<>就是构造了一个返回值为空的TerminalOp实例,我们以串行为例继续向下分析;
evaluate方法会接收这个TerminalOp实例对象,跟进到evaluate方法中

    /**
     * 使用终止操作返回最终结果
     * @param  结果类型
     * @param terminalOp 这个终止会被作用到流上
     * @return the result
     */
    final  R evaluate(TerminalOp terminalOp) {
        assert getOutputShape() == terminalOp.inputShape();
        if (linkedOrConsumed)
            throw new IllegalStateException(MSG_STREAM_LINKED);
        linkedOrConsumed = true;

        return isParallel()
               ? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))
               : terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
    }

跟进terminalOp.evaluateSequential方法,这个方法是通过PipelineHelper进行中间操作的串联,然后顺序执行这些中间操作

    /**
     * 顺序执行所有中间操作
     * @param helper 持有上游中间操作
     * @param spliterator 源
     */
     R evaluateSequential(PipelineHelper helper,Spliterator spliterator);

因为我们分析是forEach,找到它在ForEachOps中的默认实现,ForEachOpsForEachOp的一个辅助类,定义形式类似于Spliterator和Spliterators之间关系,ForEachOp实现了TerminalOpTerminalSink接口,我们跟进evaluateSequential的实现,这里通过PipelineHelper调用了wrapAndCopyInto方法,传入一个Sink对象,作为接收返回体,传入spliterator,然后找到wrapAndCopyInto的默认实现

 static abstract class ForEachOp
            implements TerminalOp, TerminalSink {

        @Override
        public  Void evaluateSequential(PipelineHelper helper, Spliterator spliterator) {
            return helper.wrapAndCopyInto(this, spliterator).get();
        }

AbstractPipelinewrapAndCopyInto做了默认实现,这里整个流发生调用的地方

    @Override
    final > S wrapAndCopyInto(S sink, Spliterator spliterator) {
        copyInto(wrapSink(Objects.requireNonNull(sink)), spliterator);
        return sink;
    }
    
    /**
     * sink的包装,每个sink代表一个阶段,或者说是一个中间操作,这里将每一步的中间操作拿到并执行得到返回结果
     * 
     * 这里对操作深度举一个例子,每个中间操作对应一个操作深度
     * list.stream().filter(e->ture).map(e->e).collection();
     *               操作深度1      操作深度2
     *@param sink 输出值
     */
    @Override
    @SuppressWarnings("unchecked")
    final  Sink wrapSink(Sink sink) {
        Objects.requireNonNull(sink);
        
        //获取当前管道的操作深度,每个中间操作对应一个操作深度,如果它的操作深度大于0,
        //执行以下循环,执行完毕将当前对象指向到前一阶段
        for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
            //这里对每一步的中间操作进行调用,实现类是我们之前分析的中间步骤,动作由我们自己传入;
            sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
        }
        return (Sink) sink;
    }
    
    
    /**
     * 这里对返回结果进行了循环调用, spliterator.forEachRemaining有很多具体的实现,例如ArrayList等,感兴趣自己去看
     */
    @Override
    final  void copyInto(Sink wrappedSink, Spliterator spliterator) {
        Objects.requireNonNull(wrappedSink);

        if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
            //
            wrappedSink.begin(spliterator.getExactSizeIfKnown());
            spliterator.forEachRemaining(wrappedSink);
            wrappedSink.end();
        }
        else {
            copyIntoWithCancel(wrappedSink, spliterator);
        }
    }

到这里,一个流的生命周期就结束了


总结

总结下Stream的整体调用过程

graph LR
源-->0个或多个中间操作
0个或多个中间操作-->终止操作

源阶段

1、通过Spliterators构造了一个Spliterator对象,它会持有一个数组的引用,Spliterator定义了循环、鉴值、分割等默认方法和一些特性值辅助流更加高效的流转
2、StreamSupport会把构造好的Spliterator对象传递给ReferencePipeline.Head,最终它会调用父类AbstractPipeline的构造方法,将Spliterator对象指向给sourceSpliterator

中间操作

中间操作在ReferencePipeline定义了具体行为,它构造了StatelessOp对象的匿名实现类,并最终会把上游的源传递给AbstractPipeline,通过给AbstractPipeline完成中间操作的串联

终止操作

终止操作会通过当前调用的AbstractPipeline循环去获取上游的中间操作,这里就是利用了流的串联,这里也是流真正发送调用的地方

你可能感兴趣的:(java8)