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接口中的接口和一些特性值,方法描述我以注释的形式标识出来
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 super T> action);
/**
* 对于每个遇到的元素会在当前线程执行给定的动作,也就是循环;
* 在所有元素消费完或是抛出异常后停止;
*
* @param action 给定动作
*/
default void forEachRemaining(Consumer super T> 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 super T> 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 extends T> c,
int characteristics) {
return new IteratorSpliterator<>(Objects.requireNonNull(c),
characteristics);
}
跟进IteratorSpliterator类,这是一个Spliterators中的静态内部类,实现了Spliterator接口,利用给定集合迭代器创建一个分割迭代器,它会持有一个集合的引用。
public IteratorSpliterator(Collection extends T> 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 extends Spliterator>> 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 super P_OUT, ? extends R> 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, E_IN, ?> 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 super E_OUT> downstream;
public ChainedReference(Sink super E_OUT> 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();
}
}
}
中间操作用到的关键类就是这些,所有的中间操作都是这个流程,但是中间操作只有在终止操作时才会被真正调用,通过中间操作可以看出流操作两大特性
大体流程如下,拿map操作来说
public final Stream map(Function super P_OUT, ? extends R> 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为例进行分析
跟进forEach方法,有两个实现方法
跟进ReferencePipeline的实现,它标注了为一个终止操作(Terminal operations)
// Terminal operations from Stream
@Override
public void forEach(Consumer super P_OUT> action) {
evaluate(ForEachOps.makeRef(action, false));
}
首先看ForEachOps.makeRef(action, false)
方法,它构造了一个TerminalOp的对象
public static TerminalOp makeRef(Consumer super T> 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
中的默认实现,ForEachOps
是ForEachOp
的一个辅助类,定义形式类似于Spliterator和Spliterators之间关系,ForEachOp
实现了TerminalOp
和TerminalSink
接口,我们跟进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();
}
在AbstractPipeline
对wrapAndCopyInto
做了默认实现,这里整个流发生调用的地方
@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循环去获取上游的中间操作,这里就是利用了流的串联,这里也是流真正发送调用的地方