JDK8 Stream源码完全解析——手写Stream

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干两件事:

  1. 把每个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。

  2. 执行计算。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中执行。

要深入理解forkjoin框架,点击这里

 

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是一颗二叉树,叶子节点就是最终要处理的数据。

 

 

 

 

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值