java 8 Stream实现原理

参考资料:

https://www.cnblogs.com/CarpenterLee/archive/2017/03/28/6637118.html

1、关键问题

  • Stream流API是如何实现的呢?

  • Pipeline是怎么执行的,每次方法调用都会导致一次迭代吗?

  • 自动并行又是怎么做到的,线程个数是多少?

2、流水线

  • Stream上的所有操作分为两类:中间操作和结束操作。

    • 中间操作根据是否受到前面的元素影响,又可以分为无状态的(Stateless)和有状态的(Stateful),比如排序是有状态操作,在读取所有元素之前并不能确定排序结果;

    • 结束操作又可以分为短路操作和非短路操作,短路操作是指不用处理全部元素就可以返回结果.

    • 之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。

  • 流水线的实现方式:

    java 7

    java 8

    int longest = 0; 
    for(String str : strings){ 
        if(str.startsWith("A")){// 1. filter(), 保留以A开头的字符串 
            int len = str.length();// 2. mapToInt(), 转换成长度 
            longest = Math.max(len, longest);// 3. max(), 保留最长的长度 
        } 
    }


    int longestStringLengthStartingWithA 
            = strings.stream() 
                  .filter(s -> s.startsWith("A")) 
                  .mapToInt(String::length) 
                  .max();


  • Stream类库设计在的问题在于并不知道用户的意图具体是什么,所以Stream流水线方案要解决的问题有:

    1. 用户操作如何记录?

    2. 操作如何叠加?

    3. 叠加之后的操作如何执行?

    4. 执行后的结果(如果有)在哪里?

2.1、操作如何记录

  • Stream使用阶段(Stage)的概念来描述完整的操作<数据来源,操作,回调函数>,跟Stream相关类和接口的继承关系图示

    • 这里的PipelineHelper的实例代表了Stage,Head表示地一个Stage,即将数据源转换为Stream流。(从StreamSupport类作为入口,可以找到上图中的类,都位于java.util.stream包内)

  • Stream流水线组织结构示意图如下:这些Stream对象以双向链表的形式组织在一起,构成整个流水线,由于每个Stage都记录了前一个Stage和本次的操作以及回调函数,依靠这种结构就能建立起对数据源的所有操作

2.2、操作如何叠加

  • 由于每个Stage只知道自己所要执行的动作,所以需要Sink接口(在ReferencePipeline类中)在协调相邻Stage之间的调用关系。

    Sink接口包含的方法

    方法名

    作用

    void begin(long size)

    开始遍历元素之前调用该方法,通知Sink做好准备。

    void end()

    所有元素遍历完成之后调用,通知Sink没有更多的元素了。

    boolean cancellationRequested()

    是否可以结束操作,可以让短路操作尽早结束。

    void accept(T t)

    遍历元素时调用,接受一个待处理元素,并对元素进行处理。Stage把自己包含的操作和回调方法封装到该方法里,前一个Stage只需要调用当前Stage.accept(T t)方法就行了。

  • 当然对于有状态的操作,Sink的begin()和end()方法也是必须重写的。比如Stream.sorted(),它是一个有状态的中间操作。

  • 对于短路操作,Sink.cancellationRequested()也是必须重写的。比如Stream.findFirst(),它是短路操作。

  • 每个Stage都会把自己的操作封装到Sink中,Sink采用[处理->转发]的模式来叠加操作。

    Stream的中间操作是如何将自身的操作包装成Sink以及Sink是如何将处理结果转发给下一个Sink

    // Stream.map(),调用该方法将产生一个新的Stream
    public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) { 
        ... 
        return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE, 
                                     StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) { 
            @Override /*opWripSink()方法返回由回调函数包装而成Sink*/ 
            Sink<P_OUT> opWrapSink(int flags, Sink<R> downstream) { 
                return new Sink.ChainedReference<P_OUT, R>(downstream) { 
                    @Override 
                    public void accept(P_OUT u) { 
                        R r = mapper.apply(u);// 1. 使用当前Sink包装的回调函数mapper处理u 
                        downstream.accept(r);// 2. 将处理结果传递给流水线下游的Sink 
                    } 
                }; 
            } 
        }; 
    }

    这里的处理都是延迟加载的。

  • Stream.sorted()方法的示例,这是一个有状态的中间操作

    // Stream.sort()方法用到的Sink实现 
    class RefSortingSink<T> extends AbstractRefSortingSink<T> { 
        private ArrayList<T> list;// 存放用于排序的元素 
        RefSortingSink(Sink<? super T> downstream, Comparator<? super T> comparator) { 
            super(downstream, comparator); 
        } 
        @Override 
        public void begin(long size) { 
            ... 
            // 创建一个存放排序元素的列表 
            list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>(); 
        } 
        @Override 
        public void end() { 
            list.sort(comparator);// 只有元素全部接收之后才能开始排序 
            downstream.begin(list.size()); 
            if (!cancellationWasRequested) {// 下游Sink不包含短路操作 
                list.forEach(downstream::accept);// 2. 将处理结果传递给流水线下游的Sink 
            } 
            else {// 下游Sink包含短路操作 
                for (T t : list) {// 每次都调用cancellationRequested()询问是否可以结束处理。 
                    if (downstream.cancellationRequested()) break; 
                    downstream.accept(t);// 2. 将处理结果传递给流水线下游的Sink 
                } 
            } 
            downstream.end(); 
            list = null; 
        } 
        @Override 
        public void accept(T t) { 
            list.add(t);// 1. 使用当前Sink包装动作处理t,只是简单的将元素添加到中间列表当中 
        } 
    }

    1、首先beging()方法告诉Sink参与排序的元素个数,方便确定中间结果容器的的大小;

    2、之后通过accept()方法将元素添加到中间结果当中,最终执行时调用者会不断调用该方法,直到遍历所有元素;

    3、最后end()方法告诉Sink所有元素遍历完毕,启动排序步骤,排序完成后将结果传递给下游的Sink;

    4、如果下游的Sink是短路操作,将结果传递给下游时不断询问下游cancellationRequested()是否可以结束处理。

2.3、叠加之后操作如何执行

  • 调用终止(Terminal)操作启动流水线。

  • 终止操作会创建一个包装了自己操作的Sink,这个Sink只需要处理数据而不需要将结果传递给下游的Sink。

  • 上游的Sink是如何找到下游Sink的?

    • 使用opWrapSink()可以将当前操作与下游Sink(上文中的downstream参数)结合成新Sink。

      // AbstractPipeline.wrapSink()  
      // 从下游向上游不断包装Sink。如果最初传入的sink代表结束操作,  
      // 函数返回时就可以得到一个代表了流水线上所有操作的Sink。  
      final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {  
          ...  
          for (AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {  
              sink = p.opWrapSink(p.previousStage.combinedFlags, sink);  
          }  
          return (Sink<P_IN>) sink;  
      }


    • 现在流水线上从开始到结束的所有的操作都被包装到了一个Sink(相当与头节点)里,执行这个Sink就相当于执行整个流水线。

    • 每个Sink的执行代码如下:

      // AbstractPipeline.copyInto(), 对spliterator代表的数据执行wrappedSink代表的操作。 
      final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) { 
          ... 
          if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) { 
              wrappedSink.begin(spliterator.getExactSizeIfKnown());// 通知开始遍历 
              spliterator.forEachRemaining(wrappedSink);// 迭代处理所有数据 
              wrappedSink.end();// 通知遍历结束 
          } 
          ... 
      }


2.4、执行的结果在哪里

  1. 对于表中返回boolean或者Optional的操作(Optional是存放 一个 值的容器)的操作,由于值返回一个值,只需要在对应的Sink中记录这个值,等到执行结束时返回就可以了。

  2. 对于归约操作,最终结果放在用户调用时指定的容器中(容器类型通过收集器指定)。collect(), reduce(), max(), min()都是归约操作,虽然max()和min()也是返回一个Optional,但事实上底层是通过调用reduce()方法实现的。

  3. 对于返回是数组的情况,毫无疑问的结果会放在数组当中。这么说当然是对的,但在最终返回数组之前,结果其实是存储在一种叫做Node的数据结构中的。Node是一种多叉树结构,元素存储在树的叶子当中,并且一个叶子节点可以存放多个元素。这样做是为了并行执行方便。关于Node的具体结构,我们会在下一节探究Stream如何并行执行时给出详细说明。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值