1、为什么使用 Stream
for 循环或者 Iterator 循环的不足
- 如果想要在一次循环迭代中完成多次操作(例如,在一次循环中,进行去重、过滤、元素转换、排序操作),会比较繁琐,甚至需要多次循环才能达到自己的目标。造成的不好的结果,一个是代码比较繁琐,一个是多次循环迭代会有性能损失(局部变量会增加,增加了内存的开支)
- 如果希望使用并行处理,需要自己编写代码
- 如果需要进行一些统计操作,需要自己编写相关代码,而这些代码往往又是比较繁杂的
Stream 的优势
- 一次迭代中支持多种操作
- 使用 Fork/Join 框架,支持了并行处理
- 提供了常见统计场景的解决方案,且支持定义自己的统计方案,十分灵活(内部提供了一套简单的 mapReduce 机制X)
- 支持函数式编程,代码风格更加简洁
2、一个简单的 Stream 栗子(后续源码分析也是根据这个例子进行)
需求是,获取一个 list 中大于 10 的前两名数字:
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>();
nums.add(3);
nums.add(13);
nums.add(33);
nums.add(23);
List<Integer> res = nums.stream()
.filter(n->n>10)
.sorted()
.limit(2)
.collect(
ArrayList::new,
ArrayList::add,
ArrayList::addAll
);
System.out.println(res.toString());
}
flunt 风格的代码,看起来很舒服。
3、Stream 使用
一般,我们使用 Stream 的时候,是以下流程:
- 构建 Stream
- 中间操作1
- 中间操作2
- 中间操作…
- 终结操作
Stream 常用的流操作包括:
-
中间操作(Intermediate Operations)
-
无状态(Stateless)操作:每个数据的处理是独立的,不会影响或依赖之前的数据。如 filter()、flatMap()、flatMapToDouble()、flatMapToInt()、flatMapToLong()、map()、mapToDouble()、mapToInt()、mapToLong()、peek()、unordered() 等
-
有状态(Stateful)操作:处理时会记录状态,比如处理了几个。后面元素的处理会依赖前面记录的状态,或者拿到所有元素才能继续下去。如 distinct()、sorted()、sorted(comparator)、limit()、skip() 等
-
-
终止操作(Terminal Operations)
- 非短路操作:处理完所有数据才能得到结果。如 collect()、count()、forEach()、forEachOrdered()、max()、min()、reduce()、toArray()等。
- 短路(short-circuiting)操作:拿到符合预期的结果就会停下来,不一定会处理完所有数据。如 anyMatch()、allMatch()、noneMatch()、findFirst()、findAny() 等。
4、Stream 结构
Stream 操作存储结构,以及相关接口和类
Stream 的操作(主要指流的构建、中间操作)是以双向链表的形式存储的,核心类是 AbstractPipeline。为了方便后续的学习,我们可以简单认为,一个 AbstractPipeline 代表流的创建操作或者是一个中间操作。Stream 中创建流和中间操作会形成一个 AbstractPipeline 双向链表,每添加一个中间操作,就会在链表结尾新增一个节点。
几个核心接口:
- BaseStream接口:主要定义了获取迭代器、支持分割的迭代器、并行流的转换、流的关闭能力
- Stream 接口:定义了常见的中间操作和终结操作,也是 API 类
几个核心的抽象类:
- PipelineHelper:定义了 sink 链表的构建、以及中间操作、终结操作的执行等能力
- AbstractPipeline:最核心的一个类,构建了一个「中间操作和创建流操作」的双向列表,同时,继承了 PipelineHelper,也拥有 sink 链表的构建、以及操作的执行等能力
- ReferencePipeline:继承了 AbstractPipeline,同时实现了 Stream 接口,提供了常见的中间操作、终结操作的能力
「操作」流水化调用相关的接口—sink
一个 sink 中封装了一个「操作」的执行过程。
Sink 接口的核心方法:
- accept():进行迭代时,会将元素推入这个方法,真正进行操作
- begin(): accept 执行之前调用的方法
- end(): accept 执行之后调用的方法
Sink 的通用实现 ChainedReference 中会记录下游的 sink 指针,所以其实整个stream 操作中 sink 也是一个链式结构(其实是装饰器模式)。
中间操作的 sink 的创建由 AbstractPipeline#opWrapSink 创建,终结操作的 sink 由 .ReduceOps.ReduceOp#makeSink 创建,sink 链的建立,由 AbstractPipeline#wrapSink 完成
终结操作相关的接口
TreminalOp 是终结操作的顶级接口,定义了并行、串行执行中间操作的能力
有四个类,ForEachOp、MatchOp、ReduceOp、FindOp 实现了该接口,所以终结操作总体可以归纳到这 4 类中
5、Stream 源码解析
5.1、构建Stream 源
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
主要有两步
- 一、构建一个 spliterator,我们可以认为一个 spliterator 是一个拥有分割元素能力的 Iterator。为了减少篇幅,这块直接略过。
- 二、 使用 StreamSupport#stream(java.util.Spliterator, boolean) 构建一个 AbstractPipeline 对象。看下源码:
public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
Objects.requireNonNull(spliterator);
return new ReferencePipeline.Head<>(spliterator,
StreamOpFlag.fromCharacteristics(spliterator),
parallel);
}
最终是返回了一个 ReferencePipeline.Head 对象。看下这个类的结构:
static class Head<E_IN, E_OUT> extends ReferencePipeline<E_IN, E_OUT> {
Head(Supplier<? extends Spliterator<?>> source,
int sourceFlags, boolean parallel) {
super(source, sourceFlags, parallel);
}
Head(Spliterator<?> source,
int sourceFlags, boolean parallel) {
super(source, sourceFlags, parallel);
}
@Override
final boolean opIsStateful() {
throw new UnsupportedOperationException();
}
// 直接抛出了异常? 因为这个操作不需要被 warpSink
@Override
final Sink<E_IN> opWrapSink(int flags, Sink<E_OUT> sink) {
throw new UnsupportedOperationException();
}
@Override
public void forEach(Consumer<? super E_OUT> action) {
if (!isParallel()) {
sourceStageSpliterator().forEachRemaining(action);
}
else {
super.forEach(action);
}
}
@Override
public void forEachOrdered(Consumer<? super E_OUT> action) {
if (!isParallel()) {
sourceStageSpliterator().forEachRemaining(action);
}
else {
super.forEachOrdered(action);
}
}
}
这个类,存在的主要意义就是提供 spliterator,提供元素的迭代能力,以及作为「操作链」的第一个节点。
5.2、添加中间操作(着重看 sink 的实现逻辑)
5.2.1、添加 filter 操作
ReferencePipeline#filter
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
Objects.requireNonNull(predicate);
return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SIZED) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
@Override
public void begin(long size) {
downstream.begin(-1);
}
@Override
public void accept(P_OUT u) {
if (predicate.test(u))
downstream.accept(u);
}
};
}
};
}
最终返回一个 StatelessOp 对象,也标志的这个操作是个无状态的中间操作。重点关注下 accept 方法,这个方法会直接调用下游 sink 的 accept 方法。
5.2.2、添加 sorted 操作
最终返回一个 SortedOps.OfRef 对象。看下这个类的 opWrapSink 方法:
@Override
public Sink<T> opWrapSink(int flags, Sink<T> sink) {
Objects.requireNonNull(sink);
if (StreamOpFlag.SORTED.isKnown(flags) && isNaturalSort)
return sink;
else if (StreamOpFlag.SIZED.isKnown(flags))
return new SizedRefSortingSink<>(sink, comparator);
else
return new RefSortingSink<>(sink, comparator);
}
看下 RefSortingSink 的实现:
private static final class RefSortingSink<T> extends AbstractRefSortingSink<T> {
private ArrayList<T> list;
RefSortingSink(Sink<? super T> sink, Comparator<? super T> comparator) {
super(sink, comparator);
}
@Override
public void begin(long size) {
// 会初始化一个 list
if (size >= Nodes.MAX_ARRAY_SIZE)
throw new IllegalArgumentException(Nodes.BAD_SIZE);
list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();
}
@Override
public void end() {
// 排序
list.sort(comparator);
// 调用下游 sink 的 begin 方法
downstream.begin(list.size());
if (!cancellationWasRequested) {
// 调用下游 sink 的accept 方法
list.forEach(downstream::accept);
}
else {
for (T t : list) {
if (downstream.cancellationRequested()) break;
downstream.accept(t);
}
}
// 调用下游的 end 方法
downstream.end();
list = null;
}
@Override
public void accept(T t) {
// 将元素加入 list
list.add(t);
}
}
为什么不在 accept 中调用下游的方法呢?sorted() 是一个有状态的操作,一般会有一个属于自己的容器,用来记录处自己理过的数据的状态。sorted() 是在执行 begin 的时候初始化这个容器,在执行 accept 的时候把数据放到容器中,最后在执行 end 方法时才正在开始排序。排序之后再将数据,采用同样的方式依次传递给下游节点。
5.2.3、添加 limit 操作
Sink<T> opWrapSink(int flags, Sink<T> sink) {
return new Sink.ChainedReference<T, T>(sink) {
long n = skip;
long m = limit >= 0 ? limit : Long.MAX_VALUE;
@Override
public void begin(long size) {
downstream.begin(calcSize(size, skip, m));
}
@Override
public void accept(T t) {
if (n == 0) {
if (m > 0) {
m--;
downstream.accept(t);
}
}
else {
n--;
}
}
@Override
public boolean cancellationRequested() {
return m == 0 || downstream.cancellationRequested();
}
};
}
accept 方法中,只会选取前 m 个元素,然后继续调用下游 sink 的 accept 方法,上游通过 cancellationRequested 方法的返回值,来判断是否还需要将更多的元素 push 到本 sink 的 accept 方法中。
5.3、添加终结操作(wrapSink、执行操作[包含中间操作、终结操作])
collect 方法:
public final <R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super P_OUT> accumulator,
BiConsumer<R, R> combiner) {
return evaluate(ReduceOps.makeRef(supplier, accumulator, combiner));
}
首先看下 ReduceOps.makeRef(supplier, accumulator, combiner) 做了什么:
public static <T, R> TerminalOp<T, R> makeRef(Supplier<R> seedFactory, BiConsumer<R, ? super T> accumulator, BiConsumer<R,R> reducer) {
Objects.requireNonNull(seedFactory);
Objects.requireNonNull(accumulator);
Objects.requireNonNull(reducer);
class ReducingSink extends Box<R>
implements AccumulatingSink<T, R, ReducingSink> {
@Override
public void begin(long size) {
state = seedFactory.get();
}
@Override
public void accept(T t) {
// 将元素推入累加器
accumulator.accept(state, t);
}
@Override
public void combine(ReducingSink other) {
reducer.accept(state, other.state);
}
}
return new ReduceOp<T, R, ReducingSink>(StreamShape.REFERENCE) {
@Override
public ReducingSink makeSink() {
return new ReducingSink();
}
};
}
返回了一个 ReduceOp 对象,ReduceOp 实现了 TerminalOp 的 makeSink 方法,返回了 makeRef 方法中定义的 ReducingSink 类型的对象。
继续看下 evaluate 方法的逻辑:
final <R> R evaluate(TerminalOp<E_OUT, R> 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:
public <P_IN> R evaluateSequential(PipelineHelper<T> helper,
Spliterator<P_IN> spliterator) {
return helper.wrapAndCopyInto(makeSink(), spliterator).get();
}
直接调用了 AbstractPipeline 中最后一个中间操作(即 limit 操作生成的 AbstractPipeline)的 wrapAndCopyInto 方法。这个方法也是分两步
- makeSink() 创建终结操作对应的 sink
- wrapAndCopyInto() warp 中间操作的 sink,并执行 sink
先看下,makeSink() 方法,就是我们上面创建的 ReduceOp 中实现的 makeSink 方法:
@Override
public ReducingSink makeSink() {
return new ReducingSink();
}
直接返回了 ReducingSink ,这个类,上面已经看过,就不赘述了,直接看下 wrapAndCopyInto 方法在拿到终结操作的 sink 后,做了什么:
@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 方法,根据 AbstractPipeline 链表,进行中间操作的 sink 链的构建。
- copyInto 方法则是真正执行中间操作和终结操作
wrapSink 方法:
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;
}
wrapSink 方法逻辑并不复杂,就是从 AbstractPipeline 链表最后一个节点开始循环(limit 节点),一直到第一个中间操作的节点(filter 节点),循环调用各个 AbstractPipeline 的 opWrapSink 方法,最终得到一个 sink,这个 sink 包含了所有中间操作和终结操作的逻辑。
接下来,看下 copyInto 方法的逻辑
final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
Objects.requireNonNull(wrappedSink);
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
wrappedSink.begin(spliterator.getExactSizeIfKnown());
spliterator.forEachRemaining(wrappedSink);
wrappedSink.end();
}
else {
copyIntoWithCancel(wrappedSink, spliterator);
}
}
从第一个中间操作对应的 sink 的 begin 方法开始执行,然后在迭代中将元素推入第一个 sink,执行 sink 链,最后执行第一个 sink 的 end 方法。
sink 装饰器对象创建的过程:
sink 装饰器对象执行的过程如下图:
6、总结
整个过程可以简单理解为:
构造 AbstractPipeline ==> 使用代理模式构造 Sink 对象 ==> 通过迭代器执行 sink 获取结果
所以,要理解 Stream 的原理,总体只需要理解一下几个过程:
- AbstractPipeline 链的构建
- AbstractPipeline ofWrapSink 的实现逻辑,因为 AbstractPipeline 中间操作的逻辑,都在 sink 里面
几个简单的问题:
- 从接口设计上,sink 的存在意义是什么
引用
- https://toutiao.io/posts/imbyvb/preview