美文网首页Java
Java 8 之Stream Pipeline

Java 8 之Stream Pipeline

作者: Unyielding_L | 来源:发表于2018-08-24 11:00 被阅读0次

    为了计算,“流”操作组成了一个流管道。一个流管道包括数据源、中间操作和终端操作。数据源可以是数组、集合、I/O通道和生成函数。而中间操作则是像过滤filter 或者map这种将一个流转换为另一个流的操作。那终端操作呢,就是产生一个结果或者别的副作用(转为集合或者统计成一个数字)。流是惰性的,源数据的计算只在终端操作启动时操作,流只在需要时消费。
    下面我们就聊聊“流”管道的那些事
    在遍历集合的时候,我们会经常写下面这样的代码:

    Integer[] strea = {1, 2, 3, 4, 6, 4, 5, 5, 6, 4};
    long count=Stream.of(strea)
                    .filter(i -> i > 4)
                   .count();
    

    下面我们便会以这个例子对"流管道"展开叙述
    在IDE 中我们可以按住Ctrl点到Stream.of()的方法中去,如下:

    public static<T> Stream<T> of(T... values) {
            return Arrays.stream(values);
        }
    

    然后紧接着进入* Arrays.stream()*方法里去:

    public static <T> Stream<T> stream(T[] array) {
            return stream(array, 0, array.length);
    }
    

    然后你就会想到我们要进入这个stream(T[] arr,int start,int end)的方法:

    public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
            return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
        }
    

    这里出现了一个工具类:StreamSupport,它提供了一个stream方法传入两个参数一个是:Spliterator实例详见,另外一个参数是:是否为并行

    public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
            Objects.requireNonNull(spliterator);
            return new ReferencePipeline.Head<>(spliterator,
                                                StreamOpFlag.fromCharacteristics(spliterator),
                                                parallel);
        }
    

    重点终于来了,翻源码是比较痛苦的,若是刚才的三步中去研究别的还不知道要花费多长时间才能看到重点:
    重点就是这个ReferencePipeline 这个类,下面我们来这个类的关系图(从Idea导出,不会玩的可以私聊):


    ReferencePipeline类继承关系图.png

    下面我们从上图带Pipeline字眼的说起,从上往下看,我们先来看下PipelineHelper 这个抽象类
    PipelineHelper ,官方的定义是用于执行流管道的帮助类,在某一个stage 输出流管道的所有信息(输出的形状、中间操作、流标志、并行性等)。至于这些信息代表什么,下面会逐个讲到。该类定义了获取流管道信息的抽象方法,直接贴代码:

    abstract class PipelineHelper<P_OUT> {
         /**获取输出形状 StreamShape 是个枚举类型: 
        REFERENCE,INT_VALUE,LONG_VALUE,DOUBLE_VALUE  
       正常都是引用类型 REFERENCE*/
        abstract StreamShape getSourceShape();
        /**获取组合标志,包含流标志和操作标志*/
        abstract int getStreamAndOpFlags();
        /** 如果已知,则返回将这个 PipelineHelper 所描述的管道阶段应用到提供的Spliterator 所描述的输入部分所产生的输出部分的精确大小。  
         如果不知道或不知道无穷大,则返回 -1。
         确定“知不知道”,则判断提供的Spliterator 是否有特征值SIZED*/
       abstract<P_IN> long exactOutputSizeIfKnown(Spliterator<P_IN> spliterator);
       /**当终端操作初始化的时候调用该方法将所有实现PipelineHelper类型的中间操作包装到给定的Sink中 ,并返回这个Sink*/
       abstract<P_IN> Sink<P_IN> wrapSink(Sink<P_OUT> sink);
       /**该方法先调用上面的wrapSink 方法,然后使用返回的Sink 处理Spliterator中的数据*/
       abstract<P_IN, S extends Sink<P_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator);
       /** 在遇到终端操作时执行该方法*/
       abstract<P_IN> Node<P_OUT> evaluate(Spliterator<P_IN> spliterator,
                                            boolean flatten,
                                            IntFunction<P_OUT[]> generator);
    }
    

    下面我来说下AbstractPipeline这个类吧
    “管道”类的抽象基类,它是流接口和流接口的原始类型的核心实现以及管理流管道的创建和evaluate(我理解的就是实际计算)。
    AbstractPipeline表示流管道的初始部分,封装了流源和零个或多个中间操作。每个 AbstractPipeline对象通常被称为stage ,其中每个stage都表示一个流源或中间操作,到这里大家就可以想象出流管道的本质就是双向链表。下面来看看它的几个属性:

    /**官方的定义:反向链接到管道链的头部,流的源头(如果本身是源stage,这里就为self)*/
    private final AbstractPipeline sourceStage;
    /** 流管道的上游,若是源stage则为null,其实就是双向链表的前指针*/
    private final AbstractPipeline previousStage;
    /** 流管道的下游,若是最后一个中间操作,则为null*/
    private AbstractPipeline nextStage;
    /** 如果是串行的则是中间操作数,如果是并行的则是有状态的操作数,执行到终端操作的时候会使用到(在evaluate()方法)*/
    private int depth;
    /** 如果此管道已连接或使用,则为真*/
    private boolean linkedOrConsumed;
    /** 数据源的Spliterator,只对管道头有效*/
    private Spliterator<?> sourceSpliterator;
    

    当然还有其它的属性,在这篇博客中我就不想写了,等待下一版。
    由上面的属性介绍,可以得出下面这个图:


    流管道抽象示意图.png

    上面这个图就是使用上面的代码示例1,画出的,因为count这个操作其实是两个操作:mapToLong(e->1L)sum()操作
    看到上图中并没有sum()操作因为终端操作时不包含在流管道里面的。
    下面要讲得可能有点难理解其实无论执行流初始化操作还是流的中间操作都是构成一个静态双向链表,真正执行的动作类是Sink 接口,该接口继承了Consumer接口,官方的定义是该类代表了流管道的每个stage,这个说法不合理,因为它同样可以代表终端操作,而终端操作不属于流管道中的某个stage。只能这么定义它,真正操作数据源的类。
    下面将Sink 部分重要代码粘贴出来,并附有注释:

    interface Sink<T> extends Consumer<T> {
        /** 重置接收器状态以接收新数据集。  
          在将任何数据发送到接收器之前必须调用此状态。
          参数size 是要传数据所包含元素的大小,若不能计算实际 的大小,可以传-1,*/
        default void begin(long size) {}
        /** 指示所有元素都已被推送。
          如果本Sink是有状态的,  此时它应该将任何存储状态发送到下游,
         并且应该清除所有累积的状态(和相关资源)。
         在此调用之前,sink必须处于活动状态,在此调用之后,它被返回到初始状态。*/
        default void end() {}
         /** 表示该Sink 不在接收其他元素,当然默认返回false*/
        default boolean cancellationRequested() {
            return false;
        }
        /** 消费一个整型的基本类型的元素*/
        default void accept(int value) {
            throw new IllegalStateException("called wrong accept method");
        }
         /** 消费一个长整型的基本类型的元素*/
        default void accept(long value) {
            throw new IllegalStateException("called wrong accept method");
        }
        /** 消费一个双精度的基本类型的元素*/
        default void accept(double value) {
            throw new IllegalStateException("called wrong accept method");
        }
    }
    

    下面来看看实现实现这个接口的抽象类ChainedReference ,根据名字我们就可知道该类也是一个链表,没有过多的注释。

      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();
            }
    
            @Override
            public boolean cancellationRequested() {
                return downstream.cancellationRequested();
            }
        }
    

    回到上文,当执行到终端的时候,会调用evaluate的方法,AbstractPipeline 的实现如下图:

     final <P_IN> Node<E_OUT> evaluate(Spliterator<P_IN> spliterator,
                                          boolean flatten,
                                          IntFunction<E_OUT[]> generator) {
            if (isParallel()) {
                // @@@ Optimize if op of this pipeline stage is a stateful op
                return evaluateToNode(this, spliterator, flatten, generator);
            }
            else {
                Node.Builder<E_OUT> nb = makeNodeBuilder(
                        exactOutputSizeIfKnown(spliterator), generator);
                return wrapAndCopyInto(nb, spliterator).build();
            }
        }
    

    为了简单起见我们先讨论串行的部分,我们不讨论第一个建造Node的用处,分别用到了三个方法:wrapAndCopyInto,copyInto,这三个方法我在上文介绍PipelineHelper 的时候已经详细介绍了下面是具体实现,结合上文的描述应该是可以理解的,在代码下面其实有一个图可以更好的理解

       
        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 <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;
        }
       
        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);
            }
        }
    

    作者根据上面的源码画出下面的流程图,供大家理解。


    流管道执行到终端操作开始向上回溯.png

    具体的请大家看源码

    相关文章

      网友评论

        本文标题:Java 8 之Stream Pipeline

        本文链接:https://www.haomeiwen.com/subject/ffztvftx.html