1、说明
本文代码基本参照JDK的实现,类名,接口名,属性名等都跟JDK保持一致
2、Stream核心原理与基本概念
2.1 流实现核心原理:opWrapsink
JDK stream以Spliterator作为Stream的数据源,用以提供需要被操作的数据,而Sink则作为输出,存储输出结果。同时JDK stream内还有很多操作,如filter,collect,flatmap等,有时也称操作为算子。
每个sink都有一个accept方法,用于接收元素。为说明流实现原理,假设filter的sink是伪码这样的:
filter sink{
Sink downStream;
accept(element){
if (element >5){
downStream.accept(element)
}
}
}
而collect的sink是这样的:
collect sink{
List list;
accept(element){
list.add(elemnt)
}
}
如果collect sink是filter sink的downStream,这样执行filter sink的accept方法,就可以实现把大于5的elemnt放入到collect sink内部的List中,从而实现对元素的过滤。而opWrapsink就是让collect sink成为filter sink的downStream的。看下面这段伪码:
filter Stream{
filter_sink;
opWrapsink(collect_sink){
filter_sink.downStream= collect_sink
}
}
由此,filter_sink和collect_sink就串起来了,可见sink是链式结构的,因而每个stream也是链式结构的,也许也正因此,JDK中对stream接口的实现类被称为pipeline。把sink串接起来的操作就称为wrap,需要注意的是,无论wrap了多少个sink,最后整个wrap的结果就是一个sink。另外还需要注意wrap的顺序问题,只能上游wrap下游,这里filter显然是上游,而collect是下游。
从这段伪码可以看出,不同的操作的区别在于sink的实现不同。这些操作的功能可以说基本都是在sink中完成,剩下的就是把sink串起来就行了
2.2 sourceStage,nextStage,preStage
每个pipeline都有sourceStage,nextStage,preStage,其中sourceStage就是最初通过传入spliterator构造出来的那个,保存着数据。而其他pipeline不保存数据。其他pipeline通过spliterator()等方法,一直往前访问preStage,直到访问到sourceStage,获取spliterator对象。下面伪码展示如何把pipeline“串”起来的:
MyStream{
spliterator
sourceStage
nextStage
preStage
depth
MyStream(spliterator){
this. spliterator= spliterator
sourceStage=this
depth=0
}
MyStream(preStage){
this.sourceStage=preStage.sourceStage
this. preStage= preStage
preStage.nexStage=this;
depth= preStage.depth+1
}
static of(spliterator){
return new MyStream(spliterator);
}
filter(){
return new MyStream(this);
}
}
这个伪码的使用形式类似下面这样:
MyStream.of(spliterator).filter();
首先使用of创建sourceStage,然后对sourceStage调用filter,创建新的stream,并传入自身this作为参数。
这里用了两套构造器完成sourceStage,nextStage,preStage的设置。
2.3 操作
JDK stream把操作分为三种,source操作(后面写为sourceOp),intermediateOperation(中间操作,后面写为interOp)和terminalOperation(终端操作,后面将写为terminalOp)。
对数据源spliterator的操作就是SourceOp,比如spliterator的trySplit方法,这个方法把spliterator内的数据切分为多个较小的部分,交给多个线程并行处理,每个线程处理其中一部分数据,从而充分利用CPU的多个处理器,加速数据处理速度。
会创建新的pipeline的操作,成为interOp,比如前面我们看到的filter。interOp把pipeline串了起来,形成一条链,仅此而已,所以interOp并不会进行真正的计算,当然也就不会有结果的输出。
terminalOp就是进行真正计算并输出结果的。terminalOp干两件事:
- 把每个pipeline的sink串起来。执行interOp后,我们有一条pipeline链,这时候执行terminalOp的wrapSink,wrapSink会沿着这条链,调用链上的每个pipeline的opWrapSink,从而把每个pipeline的sink串起来。以前面的filter sink和collect sink为例,collect是terminalOp,所以collect要执行wrapSink方法。collect的wrapSink是这样的
sink wrapSink(){ for(pipeline=this; pipeline.depth>0; pipeline= pipeline .previousStage){ pipeline .opWrapSink(); } }
注意这个串接顺序是从后往前串的,比如stream.distinct().filter().collect(),执行wrapSink后返回的是distinct的sink,而distinct的sink的downstream是filter的sink,而filter的sink就是collect的sink。
- 执行计算。terminalOp有2个方法用于执行计算,一个是evaluateParallel一个是evaluateSequential,分别以并行和串行的方式进行计算,获取结果。
3、Stream简单实现
本节仅实现Stream的串行计算,理清整个stream是怎么工作的,然后再分析JDK stream是怎么进行并行计算和优化的。
所需写的类和包:
一些类的说明:
- pipelineHelper,主要对sink执行wrap
- collect底层是使用ReduceOps实现的
- ReduceOps最终返回一个terminalOp接口对象。
- collector是一个接口,看一下collector的一个实现类伪码,看是怎么工作的:
collector{
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<T>, T> accumulator() {
return (state,element) -> {
System.out.println("accumulator:"+state+" "+element);
state.add(element);
};
}
}
accumulator返回一个BiConsumer,这个BiConsumer的state就是supplier()的返回值。所以整个collector的意思是,把接收操的element添加到supplier()所返回的List中
- terminalOp 用于执行最终操作。看一下一个terminalOp对象的实现的伪码:
terminalOp{
collector
terminalOp(collector){
this.collector=collector
}
makeSink(){
return sink{
accept(element){
//这里把element添加到collector. supplier()返回的list中
collector. accumulator().accept(collector. supplier(), element)
}
}
}
evaluate(pipelineHelper,spliterator){
//把terminal的sink跟所有stream的sink wrap起来
sink=pipelineHelper.wrapsink(makesink());
//这一步是遍历spliterator的每个元素,并调用sink的accept方法接收这些元素。
pipelineHelper.copyInto(sink, spliterator)
}
}
3.1 实现目标
我们希望像下面这样使用我们自己写的stream,基本跟JDK的stream使用一样:
import static java.lang.Character.isDigit;
import org.junit.Test;
import com.my.stream.Stream;
import static java.util.Arrays.asList;
import static com.my.stream.Collectors.*;
import static org.junit.Assert.assertEquals;
import java.util.List;
public class MyStream {
@Test
public void myStreamTest() throws Exception{
List<String> beginningWithNumbers=Stream.of("a", "abc1","1abc", "2cb","3dd","2cb")
.filter(value -> isDigit(value.charAt(0)))
.collect(toList());
System.out.println(beginningWithNumbers);
assertEquals(asList("1abc", "2cb","3dd","2cb"), beginningWithNumbers);
}
}
这里仅考虑实现串行功能,理清整个stream实现脉络
3.2 实现
3.2.1 Stream接口
import java.util.function.Predicate;
public interface Stream <T> {
Stream<T> filter(Predicate<? super T> predicate);
<R, A> R collect(Collector<? super T, A> collector);
@SuppressWarnings("unchecked")
public static<T> Stream<T> of(T... values){
return Arrays.stream(values);
}
}
3.2.2 Arrays类
public class Arrays {
@SuppressWarnings("unchecked")
public static <T> Stream<T> stream(T[] array){
return StreamSupport.stream((Spliterator<T>) Spliterators.spliterator(array), false);
}
}
3.2.3 StreamSupport类
public class StreamSupport {
public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel){
return new ReferencePipeline.Head<>(spliterator, parallel);
}
}
3.2.4 ReferencePipeline类
import java.util.Objects;
import java.util.function.Predicate;
public abstract class ReferencePipeline<P_IN, P_OUT> extends AbstractPipeline<P_IN, P_OUT>
implements Stream<P_OUT>{
public ReferencePipeline(AbstractPipeline<?, P_IN> previousStage) {
super(previousStage);
}
public ReferencePipeline(Spliterator<?> source, boolean parallel) {
super(source, parallel);
}
@Override
public Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
Objects.requireNonNull(predicate);
return new StatelessOp<P_OUT, P_OUT>(this) {
@Override
Sink<P_OUT> opWrapSink( Sink<P_OUT> sink) {
return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
@Override
public void accept(P_OUT u) {
if (predicate.test(u))
downstream.accept(u);
}
};
}
};
}
@SuppressWarnings("unchecked")
@Override
public <R, A> R collect(Collector<? super P_OUT, A> collector) {
return (R)evaluate(ReduceOps.makeRef(collector));
}
static class Head<E_IN, E_OUT> extends ReferencePipeline<E_IN, E_OUT>{
public Head(Spliterator<?> source, boolean parallel) {
super(source, parallel);
}
@Override
final Sink<E_IN> opWrapSink( Sink<E_OUT> sink) {
throw new UnsupportedOperationException();
}
}
abstract static class StatelessOp<E_IN, E_OUT> extends ReferencePipeline<E_IN, E_OUT> {
public StatelessOp(AbstractPipeline<?, E_IN> previousStage) {
super(previousStage);
}
}
}
3.2.5 AbstractPipeline类
主要的功能就是把stream组装起来,也就是wrap。
import java.util.Objects;
public abstract class AbstractPipeline<E_IN, E_OUT> extends PipelineHelper<E_OUT>{
@SuppressWarnings("rawtypes")
private final AbstractPipeline sourceStage;
@SuppressWarnings("rawtypes")
private final AbstractPipeline previousStage;
// @SuppressWarnings("rawtypes")
// private AbstractPipeline nextStage;
private Spliterator<?> sourceSpliterator;
private int depth;
private boolean linkedOrConsumed;
private static final String MSG_STREAM_LINKED = "流已经被操作过或已经被关闭";
private static final String MSG_CONSUMED = "source already consumed or closed";
AbstractPipeline(Spliterator<?> source, boolean parallel) {
this.previousStage = null;
this.sourceSpliterator = source;
this.sourceStage = this;
this.depth = 0;
}
AbstractPipeline(AbstractPipeline<?,E_IN> previousStage){
if (previousStage.linkedOrConsumed)
throw new IllegalStateException(MSG_STREAM_LINKED);
this.previousStage = previousStage;
this.sourceStage = previousStage.sourceStage;
this.depth = previousStage.depth + 1;
}
private Spliterator<?> sourceSpliterator() {
if (sourceStage.sourceSpliterator != null) {
Spliterator<?> spliterator = sourceStage.sourceSpliterator;
sourceStage.sourceSpliterator = null;
return spliterator;
}
throw new IllegalStateException(MSG_CONSUMED);
}
/**
* operation把接收的sink转为一个新的sink,这个新的sink用于接收输入元素
* 因此这个新的sink可以更改sink的accept逻辑,比如{@code ReferencePipeline}的filter操作
* 就是在accept中实现过滤逻辑
* @param sink
* @return
*/
abstract Sink<E_IN> opWrapSink(Sink<E_OUT> sink);
@Override
final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
Objects.requireNonNull(wrappedSink);
wrappedSink.begin(spliterator.estimateSize());
spliterator.forEachRemaining(wrappedSink);
wrappedSink.end();
}
@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(sink);
}
return (Sink<P_IN>) 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;
}
final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
if (linkedOrConsumed)
throw new IllegalStateException(MSG_STREAM_LINKED);
linkedOrConsumed = true;
return terminalOp.evaluateSequential(this, sourceSpliterator());
}
}
3.2.6 PipelineHelper
public abstract class PipelineHelper<P_OUT> {
abstract<P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator);
abstract<P_IN> Sink<P_IN> wrapSink(Sink<P_OUT> sink);
abstract<P_IN, S extends Sink<P_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator);
}
3.2.7 sink
import java.util.Objects;
import java.util.function.Consumer;
public interface Sink<T> extends Consumer<T> {
default void begin(long size) {}
default void end() {}
static abstract class ChainedReference<T, E_OUT> implements Sink<T> {
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();
}
}
}
3.2.8 Spliterator
import java.util.function.Consumer;
/**
*
* @author
*
* @param <T> 元素的类型
*/
public interface Spliterator<T> {
/**
* 遍历并处理数据源的元素
* @param action
*/
void forEachRemaining(Consumer<? super T> action) ;
long estimateSize();
}
3.2.9 Spliterators
import java.util.function.Consumer;
public final class Spliterators {
public static <T> Spliterator<T> spliterator(Object[] array) {
//省略检查参数的步骤
//checkFromToBounds(Objects.requireNonNull(array).length, fromIndex, toIndex);
return new ArraySpliterator<>(array);
}
static final class ArraySpliterator<T> implements Spliterator<T>{
private final Object[] array;
private int index;
private final int fence;
//fence是数组的边界,通过指定初始的index跟fence,可以截取数组的一个子集,不过我们这里暂时不支持截取子集,默认处理数组的所有元素
public ArraySpliterator(Object[] array) {
this.array = array;
fence= array.length;
}
@SuppressWarnings("unchecked")
@Override
public void forEachRemaining(Consumer<? super T> action) {
Object[] a; int i, hi; // hoist accesses and checks from loop
if (action == null)
throw new NullPointerException();
if ((a = array).length >= (hi = fence) &&
(i = index) >= 0 && i < (index = hi)) {
do { action.accept((T)a[i]); } while (++i < hi);
}
}
@Override
public long estimateSize() { return (long)(fence - index); }
}
}
3.2.10 Collector
import java.util.function.BiConsumer;
import java.util.function.Supplier;
/**
*
* @author
*
* @param <T> the type of input elements to the reduction operation
* @param <A> the mutable accumulation type of the reduction operation (often hidden as an implementation detail)
*/
public interface Collector<T, A> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
//BinaryOperator<A> combiner();
}
3.2.11 ReduceOps
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class ReduceOps {
/**
*
* @param <T> 输入元素的类型
* @param <I> intermediate reduction结果的类型
* @param collector a {@code Collector} defining the reduction
* @return a {@code ReduceOp} implementing the reduction
*/
public static <T, I> TerminalOp<T, I> makeRef(Collector<? super T, I> collector) {
Supplier<I> supplier = Objects.requireNonNull(collector).supplier();
BiConsumer<I, ? super T> accumulator = collector.accumulator();
//BinaryOperator<I> combiner = collector.combiner();
class ReducingSink extends Box<I>
implements AccumulatingSink<T, I, ReducingSink> {
@Override
public void begin(long size) {
state = supplier.get();
}
@Override
public void accept(T t) {
accumulator.accept(state, t);
}
}
return new ReduceOp<T, I, ReducingSink>() {
@Override
public ReducingSink makeSink() {
return new ReducingSink();
}
};
}
private static abstract class ReduceOp<T, R, S extends AccumulatingSink<T, R, S>>
implements TerminalOp<T, R> {
public abstract S makeSink();
@Override
public <P_IN> R evaluateSequential(PipelineHelper<T> helper, Spliterator<P_IN> spliterator) {
return helper.wrapAndCopyInto(makeSink(), spliterator).get();
}
}
private interface AccumulatingSink<T, R, K extends AccumulatingSink<T, R, K>> extends TerminalSink<T, R> {
}
private static abstract class Box<U> {
U state;
Box() {} // Avoid creation of special accessor
public U get() {
return state;
}
}
}
3.2.12 Collectors
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class Collectors {
public static <T>
Collector<T, List<T>> toList() {
return new Collector<T, List<T>>() {
@Override
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<T>, T> accumulator() {
return (state,element) -> {
System.out.println("accumulator:"+state+" "+element);
state.add(element);
};
}
};
}
}
3.2.13 TerminalOp
interface TerminalOp<E_IN, R> {
<P_IN> R evaluateSequential(PipelineHelper<E_IN> helper,Spliterator<P_IN> spliterator);
}
3.2.14 TerminalSink
import java.util.function.Supplier;
interface TerminalSink<T, R> extends Sink<T>, Supplier<R> { }
3.3 运行
现在可以运行实现目标中的代码了。可以看到已经实现了功能
4 理解JDK中对Stream 的优化
stream API语义明晰,代码可读性强,比如用stream替代传统的for等循环结构,能有更好的代码可读性。另外一个重要的点是,stream实现并行性相对简单,所以现在来理解下JDK是怎么实现stream的并行性的
4.1 理解StreamOptFlag
可以为操作和数据源spliterator设置标记,为优化提供信息。StreamOptFlag是一个枚举,每个枚举值就是一个标记,当前有
- DISTINCT:如果操作带有DISTINCT标记,表示操作返回的值是没有重复的。如果数据源带这个标记,表示数据源不存在重复值。
- SORTED:表示对数据进行排序
- ORDERED:表示数据是有序的
- SIZED:表示数据是有界的
- SHORT_CIRCUIT:表示操作是可中断,可取消的
StreamOptFlag用一个nible(半个字节,即两位)来表示一个标记,比如用最后两位表示DISTINCT,用最后第三和第四位表示SORTED,以此类推。比如
0000 0000 0000 0000 0000 0000 0000 0101
注意最后一个字节是0101,表示设置了DISTINCT和SORTED。这里值为01表示设置了某个标记,00是没设置,10是取消设置,11是设置和保留的意思。因为不同标记所处的字节位置不同,所以引申出position这个属性。DISTINCT的position是0,这个意思是,DISTINCT处于最后一个nible,然后从右到左前进两位就是SORTED的位置,所以SORTED的position是1。以此类推,每两位就是一个position。但是SHORT_CIRCUIT是例外,SHORT_CIRCUIT的position是12。
StreamOptFlag内部有一个枚举,Type,表示标记的类型,有如下几个值:
- SPLITERATOR,跟spliterator相关
- STREAM,跟stream相关,剩下的类型也可以顾名思义
- OP
- TERMINAL_OP
- UPSTREAM_TERMINAL_OP
StreamOptFlag搞了那么多枚举值,看起来似乎很复杂,但是做的事却很简单,主要是给操作或者数据源赋予一些标记,比如,对于stream可能会有DISTINCT,SORTED,ORDERED,SIZED这四个标记,具有这4个标记的stream如下:
0000 0000 0000 0000 0000 0000 0101 0101
这个称为stream mask(stream掩码)。而如果一个stream只具有DISTINCT,则把这个stream的标记编码跟stream mask位与一下,即对下面这两个编码进行位与:
0000 0000 0000 0000 0000 0000 0000 0001
0000 0000 0000 0000 0000 0000 0101 0101
得出的结果为
0000 0000 0000 0000 0000 0000 0000 0001
这里之所以要位与,是因为实际的标记编码,可能不会仅有stream,但是你只想留下stream相关的部分,比如你的编码同时具有SHORT_CIRCUIT和DISTINCT标记。
整个类实现比较简单,基本就是构造不同type的掩码,或者判断编码是否具有某个标记。所以下面挑一个稍微难理解的toStreamFlags方法来说说它的实现:
static int toStreamFlags(int combOpFlags) {
return ((~combOpFlags) >> 1) & FLAG_MASK_IS & combOpFlags;
}
整个方法实现及其简单,这个方法是获取一个编码的stream相关的标记,其他标记会被去除,而且如果一个stream标记是11,也会被去除。比如你这样调用:
toStreamFlags(0b0000 0000 0000 0000 0000 0000 0000 0111)
最后两位是11,其他都是0,输出结果为4,即最后两个1被置为0了。
下面分析下位移一下怎么就能做出这种结果。
任意一个nible,向右位移1位后,位移后的结果跟这个nible的上一个nible有关。由此我们仅需分析4位即可,该nible所在的两位,以及该nible的前两位。
X1X2 11 这里X1和X2的值是0或1
求非后,后两位一定是00,所以向右移一位,右移后最后一位一定是0,而mask对应的nible则位01,所以跟mask位与之后,11会变成00。
然后看一下怎么使用SHORT_CIRCUIT进行取消。看下面伪码:
while(!flag has SHORT_CIRCUIT){
container.add(element)
}
这段伪码的意思是,如果flag没有SHORT_CIRCUIT就一直把元素添加到container,直到flag被设置了SHORT_CIRCUIT。所以这里要取消container添加元素的操作很简单,只需给flag设置SHORT_CIRCUIT即可。
4.2 其他标记
collector和spliterator有更多的标记,可以通过调用characteristics()方法获得。StreamOptFlag的标记仅在stream操作中使用,而collector有很多在其自身中使用的标记,比如concurrent。当你使用Collectors.toConcurrentMap()时,就会自动设置concurrent标记。同样的spliterator上也有一些标记,用于自身操作。
4.3 并行实现
Stream底层是通过forkjoin实现并行的。比如ReduceOps,其串行版本我们前面实现过,是在terminalOp的evaluateSequential中实现的,而并行版本则在terminalOp的evaluateParallel方法中实现。看一下官方源码:
public <P_IN> R evaluateParallel(PipelineHelper<T> helper,
Spliterator<P_IN> spliterator) {
return new ReduceTask<>(this, helper, spliterator).invoke().get();
}
这里ReduceTask继承了AbstractTask,而AbstractTask继承了CountedCompleter,CountedCompleter则继承ForkJoinTask,最终把任务放到ForkJoinPool的common这个线程池中去执行。所以最终实际的Task要实现的是compute方法,用于实现计算逻辑,以及把当前任务划分为小任务丢到ForkJoinPool的common中执行。
4.4 减少自动装箱开销:StreamShape
java中int,long,double都有装箱类型,分别为Integer,Long,Double。显然,装箱类型比原生类型占用更多内存。以int为例,int占用4个字节,而Interger占用16字节,所以在集合或数组中,尤其是数据量大的情况下,使用装箱类型比使用原生类型所需的内存要大的多。在最坏的情况下, 同样大小的数组, Integer[] 要比 int[] 多占用 6 倍内存。
stream API为解决这个问题,设计了StreamShape,这是一个枚举类型,有4个值,分别为int,long,double和reference,每个值都有一套极其相似的,自己的API。
5 理解状态
前面我们说过stream操作分为sourceOp,interOp和terminalOp。实际上,根据是否有状态,stream还可以分为有状态操作(StatefulOp)和无状态操作(StatelessOp)。那么何为状态?
比如有数据格式是这样的:user:URL,用以表示用户访问了哪个URL。在流引擎(如flink)中,假设我们要处理数据,把每条数据的用户部分给去掉,然后存储起来,因为操作可以直接对单条数据进行,所以不会产生中间状态。如果是要统计用户访问某个url的次数,比如来了条数据A:URL1,此时记录下用户A访问了1次URL1,然后又来一条A:URL1,此时记录用户A访问了2次URL1。从这两个场景中,我们看到,在一个操作中,如果数据之间没有依赖关系,就不会产生状态;如果数据之间有依赖性,比如刚刚对用户访问某个URL进行求和的操作中,数据和数据间是有依赖关系的,即对一条数据的处理依赖于操作对上一条数据的处理结果,那就会产生状态。
JDK stream目前有三个状态操作的实现(其他操作都是无状态操作),如下面类层次结构图所示:
对于状态操作,显然不能像前面那样,通过在sink实现相关逻辑来实现。以distinctOp为例,要在sink中实现对sourceStage的spliterator中的数据进行distinct操作,你怎么实现这个逻辑?前面我们看到stream对数据的处理是流水线形式的,每次从spliterator中获取一条数据,然后就把数据送进sink的流水线,即放到sink的accept方法中去处理。如果作为interOp或terminalOp来实现,则你怎么知道每条到来的数据是否跟之前的重复了?所以distinct的处理不能放到sink。AbstractPipeline有一个sourceSpliterator方法,是用于获取数据源的,通常它会返回spliterator。如果在它返回spliterator之前,对这个spliterator进行处理,处理成不存在重复元素的spliterator,然后再返回,问题即可解决。这就是JDK Stream对有状态操作的处理。sourceSpliterator实现伪码如下:
Spliterator<?> sourceSpliterator(){
spliterator= getSpliteratorFromSourcestage();
//如果stream中存在有状态的操作
if(sourceAnyStateful){
//从sourcestage开始遍历pipeline链,直到当前pipeline
for(from sourceStage to currentPipeline){
//如果当前pipeline是有状态的操作
if(pipeline. opIsStateful()){
//如果当前pipeline是distinct的话,那么opEvaluateParallelLazy会把
//spliterator处理成没有重复数据的spliterator,通过把数据拷贝到set中即可实现
spliterator=pipeline .opEvaluateParallelLazy(spliterator)
}
}
}
}
5.1 spliterator与Node
刚刚我们看到状态操作是通过opEvaluateParallelLazy方法完成的,不同的状态操作,opEvaluateParallelLazy实现不同,有的直接覆盖opEvaluateParallelLazy进行实现,有的则在opEvaluateParallelLazy中调用opEvaluateParallel来实现。不过不管怎样,最终都是把spliterator处理成Node,然后通过处理Node来处理spliterator的数据。为何要这样做?我们知道,无状态操作直接操作sink即可,而且可以在内部把操作处理成forkjointask,从而实现并行处理。同样的,状态操作虽然不操作sink,但是它可以在sourceSpliterator()中处理spliterator,而且同样也支持并行处理。
Spliterator有trySplit方法,可以切分数据,然后每个task处理一部分数据,而这个task也是forkjointask。trySplit通常使用二分法进行切分,举个例子,假设你每个task只处理2个数据,Spliterator中一共10个数据,伪码如下:
// spliterator是原始的带完整数据的spliterator
Spliterator rightSplit=spliterator
// 调用trySplit后,leftSplit获得其中5个数据,rightSplit则留下剩下的5个数据
//之后看切分后数据是否小于等于2,是则处理数据,否则继续切分
Spliterator leftSplit = rightSplit.trySplit()
二分的过程中,就引申出node的概念,分成的两半就是node的左右子节点。最后的结果就是node是一颗二叉树,叶子节点就是最终要处理的数据。