Stream流sink的包装解析及元素流转

461 阅读6分钟

image.png

一直想写一个Stream流源码解析的专栏,但是奈何现在水平还不行,很难讲到面面具到。而且在最近看到了一个Java8 Stream流的源码解析专栏,讲得也是非常的好,整体的结构架子也是非常清晰。

二话不说,先给上专栏链接:juejin.cn/column/7099…

本人也向作者询问了大体文章思路,深受启发。但深知自己对Stream流的整体框架还把握的不够到位且很难用从全局表述清楚,故暂时放弃此专栏的文章整理。

但是这并不妨碍我对于Stream流局部核心源码的解析。故有了此篇文章。

文章分析哪部分的源码

如标题所述,本文主要讲解stream流sink的包装解析以及元素流转源码

文章会用一个最简单的例子以及源码跟踪的方式来讲述这部分源码。文章对于没读过Stream流源码的朋友可能依然有些不友好,但这已经是我觉得最简单的解析方式。

源码分析

总述

想了好久,我觉得还是使用类似于prep的方法来写这篇文章,效果会好上不少。

结论:1.管道的装配是从前到后的。2.sink的包装是从后到前的。3.元素的处理自然是从前到后的。

例子

sex.png

list.stream()方法

listStream.png

Head是ReferencePipeline的内部类,最后的构造方法是

listStream2.png

.map()方法

listMap.png

我们关注于返回值StatelessOp,这个无状态操作也是ReferencePipeline的内部类。(至于什么是无状态,什么是有状态,什么是头部,什么是终止操作等,本文都不予讨论,可以阅读上方专栏文章。)最后的构造方法是

listMap2.png

ReferencePipeline词如其意,就像是个管道,有前一节previousStage,后一节nextStage,以及头部sourceStage和深度depth。这样的话可以简单的认为map方法的管道连接成功了!

.filter()

listFilter.png

filter方法和map方法基本一模一样只是重写了返回内部类对应的方法而已。 其次依然是上方同map一样的构造方法这样就连接成功了filter管道。

在此便可以说明管道的连接是从前到后的。

注:本文不采用比较复杂的流程操作,因为那些东西不是本文讨论的重点。

.collect()

包装sink方法在最后的终止操作之中,所以不得不对.collect()方法做简单的分析。

listCollect.png

上图中的makeRefevaluate这两个方法都非常重要,都需要好好解析

makeRef()

makeRef返回的终止操作,其实也可以抽象的理解为管道的最后一节

public static <T, I> TerminalOp<T, I>
makeRef(Collector<? super T, I, ?> collector) {
    //我们传进来的collector主要就是supplier、BiConsumer、BinaryOperator(并行时使用)
    //就比如Collectors.toList(),简单来说就是supplier提供初始化list,BiConsumer类似于add方法
    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);
        }

        @Override
        public void combine(ReducingSink other) {
            state = combiner.apply(state, other.state);
        }
    }
    
    //这个终止操作的返回值为ReduceOp,重写了makeSink()方法,在包装sink的时候会提供最初的sink
    //返回值很有意思是ReducingSink也就是上方的局部内部类,这个局部内部类可以操作supplier、  
    //BiConsumer、BinaryOperator ,这样也就提供了最后一节管道的操作。 
    return new ReduceOp<T, I, ReducingSink>(StreamShape.REFERENCE) {
        @Override
        public ReducingSink makeSink() {
            return new ReducingSink();
        }

        @Override
        public int getOpFlags() {
            return collector.characteristics().contains(Collector.Characteristics.UNORDERED)
                   ? StreamOpFlag.NOT_ORDERED
                   : 0;
        }
    };
}

evaluate()

listeva.png

listeva2.png

这边的makeSink()其实也可以简单的理解为最后一节管道对象。

核心方法wrapAndCopyInto()

listeva3.png

首先分析包装sink方法

@Override
@SuppressWarnings("unchecked")
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
    Objects.requireNonNull(sink);
    //这个for循环非常有意思!
    //首先必须清楚AbstractPipeline.this是最后第二节管道也就是filter管道。为什么?因为evaluate方法的调
    //用者为AbstractPipeline,而管道对象为ReferencePipeline继承自AbstractPipeline。所以最后是装配到
    //filter管道,所以AbstractPipeline.this是filter管道。
    //filter管道深度是2所以进入循环包装filter管道的sink,在此处也见过了非常熟悉的opWrapSink方法
    //map和filter的返回值都重写了这个方法,下面会仔细分析这个方法
    //第一次遍历结束后p变成了上一节管道也就是map管道,深度为1继续包装map管道的sink
    //包装完后上一节管道为head管道即第一节管道,深度为0至此循环结束,包装sink结束,返回sink
    //在此便可以说明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;
}

opWrapSink方法需要仔细分析

首先看filter管道的opWrapSink方法

首先附上makeRef中的局部内部类,也就是最后一节管道,用于下面代码跟踪

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

    @Override
    public void combine(ReducingSink other) {
        state = combiner.apply(state, other.state);
    }
}
@Override
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) {
            //opWrapSink的返回值为Sink接口的内部类ChainedReference
            //构造方法为传入的sink即为下游处理器
            //可以重写begin,end,accept等方法用于不同的元素处理
            //现在列子中传入sink即为终止操作的对象也可理解为最后一节管道
            return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                @Override
                public void begin(long size) {
                    //此出列子中掉用即为makeRef中那个局部内部类的begin方法
                    //创建一个list对象
                    downstream.begin(-1);
                }

                @Override
                public void accept(P_OUT u) {
                    if (predicate.test(u))
                        //此出列子中掉用即为makeRef中那个局部内部类的accept方法
                        //往list中加入元素
                        downstream.accept(u);
                }
            };
        }
    };
}

listdown.png

接着看map管道的opWrapSink方法

@Override
@SuppressWarnings("unchecked")
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
    Objects.requireNonNull(mapper);
    return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
                                 StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
        @Override
        Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
            //此处的sink对象即为包装好的filter管道以及最后一节管道
            return new Sink.ChainedReference<P_OUT, R>(sink) {
                @Override
                public void accept(P_OUT u) {
                    //此处accept方法就会去调用filter管道的accept方法
                    //也就是去进行数据的过滤
                    //至此与元素流转其实也已经明了
                    downstream.accept(mapper.apply(u));
                }
            };
        }
    };
}

最后分析copyInto方法即为元素流转方法

listc.png

定位到ArrayListSpliterator类,是ArrayList的内部类。

public void forEachRemaining(Consumer<? super E> action) {
    //action其实就是包装好的sink对象也可以说是ChainedReference对象
    int i, hi, mc; // hoist accesses and checks from loop
    ArrayList<E> lst; Object[] a;
    if (action == null)
        throw new NullPointerException();
    if ((lst = list) != null && (a = lst.elementData) != null) {
        if ((hi = fence) < 0) {
            mc = lst.modCount;
            hi = lst.size;
        }
        else
            mc = expectedModCount;
        if ((i = index) >= 0 && (index = hi) <= a.length) {
            for (; i < hi; ++i) {
                @SuppressWarnings("unchecked") E e = (E) a[i];
                //掉用sink的第一层也就是map管道
                //然后map那边的downStream会去掉filter的accept
                //最后filter的downStream会去掉最后一节管道的的accept即把数据装入list
                action.accept(e);
            }
            if (lst.modCount == mc)
                return;
        }
    }
    throw new ConcurrentModificationException();
}

至此,Stream流sink的包装解析以及元素流转大致也就很清楚了。 能看到此处的也不容易,非常感谢!

如文中有错误或不妥之处请不吝指教,再次感谢!