java Stream 初探(一)

一、概述

支持顺序和并行聚合操作的元素序列。为了执行计算,流操作被组合成一个流管道。 流管道由源(可能是数组、集合、生成器函数、I/O 通道等)、零个或多个中间操作(将流转换为另一个流,例如filter(Predicate) ) 和终操作(产生结果或副作用,例如count()或forEach(Consumer) )。 流是懒惰的; 对源数据的计算只在终操作启动时进行,源元素只在需要时被消费。

多数数据流操作都接受一些lambda表达式参数,函数式接口用来指定操作的具体行 为。这些操作的大多数必须是无干扰(它们不修改流源)而且是无状态的(它们的结果不应依赖于在流管道执行期间可能发生变化的任何状态)

流管道可以顺序或并行执行。 这种执行模式是流的一个属性。 流是通过初始选择顺序或并行执行来创建的。 (例如, Collection.stream()创建一个顺序流, Collection.parallelStream()创建一个并行流。)这种执行模式的选择可以通过sequential()或parallel()方法进行修改,并且可以使用isParallel()方法。

二、操作分类

中间操作:中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作。

无状态是指每个元素的处理是独立的的,处理不受之前元素的影响或不依赖于外部作用域中任何在操作过程中可变的变量或状态

有状态是指后面元素的处理会依赖前面记录的某种状态或该操作只有拿到所有元素之后才能继续下去。

(例如:.distinct()操作是有状态的,该操作会依赖外部一个set集合,通过该集合收集元素且判断当前元素是否存在于该集合)

结束操作:又可以分为短路与非短路操作。

短路是指遇到符合预期元素就可以得到最终结果,不一定会处理完所有数据。

非短路是指必须处理所有元素才能得到最终结果。

中间操作无状态 filter()  map()  flatMap() peek()等
有状态distinct()  sorted()  limit()等
结束操作非短路forEach()  reduce() collect()  max() min() count()等
短路anyMatch() allMatch() noneMatch() findFirst() 等

 

三、流管道分析 

  使用以下示例简单分析

        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("11");
        list.add("111");
        List<Integer> collect = list.stream()
                .filter(Objects::nonNull)
                .map(Integer::parseInt)
                .collect(Collectors.toList());

问题一:我们知道流是懒惰的,对源数据的计算只在终操作启动时进行。那么中间的操作是如何记录的呢?

从上述代码源码中可以组装出如下管道流水线。

 可以看出每一步都会产生新的Stream。这些Stream对象以双向链表的形式组织在一起,构成整个流水线。每个Stage都记录了前一个Stage和本阶段的操作以及回调函数(lambda表达式)。这样就把所有的操作记录下来了。

问题二:各个stage之间是如何控制的?是如何将自己处理后的值传递给下一个stage的呢?答案是Sink。

Sink:Consumer的扩展,用于在流管道的各个阶段传递值,附加的方法用来管理大小信息、控制流等。 在第一次调用Sink上的accept()方法之前,您必须先调用begin()方法来通知它有数据来了(可选地通知接收器有多少数据来了),并且在所有数据发送结束之后,您必须调用end()方法。

 Sink还提供了一种机制,通过该机制,sink 可以合作发出信号,表示它不希望接收更多数据(cancellationRequested()方法),源可以在向Sink发送更多数据之前轮询该数据。接收器可能处于两种状态之一:初始状态和活动状态。 它以初始状态开始; begin()方法将其转换为活动状态,而end()方法将其转换回初始状态,在那里它可以被重用。 数据接受方法(例如accept()仅在活动状态下有效。

流管道由一个源、零个或多个中间阶段(例如过滤或映射)和一个终端阶段(例如减少或 for-each)组成。Sink实例用于表示此管道的每个阶段,无论该阶段接受对象、整数、长整数还是双精度数。

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

上述代码中,管道的入口点是过滤阶段的Sink器,它将一些元素发送到下游映射阶段的Sink器,映射阶段的Sink器然后将整数值发送到下游减少阶段Sink器(max是通过reduce实现的)。 与给定阶段关联的Sink实现应该知道下一阶段的数据类型,并在其下游Sink上调用正确的accept方法。 同样,每个阶段都必须实现与其接受的数据类型相对应的正确accept方法。

sink方法介绍
方法描述参数
begin(int size)重置接收器状态以接收新数据集。 这必须在向接收器发送任何数据之前调用要向下游推送的数据的确切大小(如果已知)或-1如果未知或无限)

 end()

表示已推送所有元素。 如果Sink是有状态的,它此时应该向下游发送任何存储的状态,并且应该清除所有累积的状态(和相关资源) 

 boolean cancellationRequested()

表示此Sink不想再接收任何数据。可以让短路操作尽早结束。 

 accept(T t)

遍历元素时调用,接受一个待处理元素,并对元素进行处理。处理完成后调用后一个阶段的accept(T t)方法。(有状态的不一定)需要处理的元素

通过源码可以发现,Stream API实现的本质就是如何重载Sink的这四个接口方法。 

问题三:操作是如何执行的?

以以下精简版的collect为例进行说明。

    public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) {
        A container = evaluate(ReduceOps.makeRef(collector));

        return (R) container;
    }

# ReduceOps.makeRef

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

            @Override
            public void combine(ReducingSink other) {
                state = combiner.apply(state, other.state);
            }
        }
        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;
            }
        };
    }

该方法返回了一个终止操作,该终止操作的makeSink方法返回的就是一个包装了终止操作的Sink。该sink也是整个调用链的出口。

# AbstractPipeline.evaluate:使用终端操作评估管道以产生结果(精简版,删除了并行流的执行)

    final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
        return  terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
    }

 这里的this指的是终止操作之前的Stream。已最上边示例来说就是map映射返回的Stream

# ReduceOp.evaluateSequential:使用指定的PipelineHelper对操作执行顺序评估,它描述了上游中间操作。(当前是终止操作,该helper是map映射返回的Stream,所以说是上游)

       @Override
        public <P_IN> R evaluateSequential(PipelineHelper<T> helper,
                                           Spliterator<P_IN> spliterator) {
            return helper.wrapAndCopyInto(makeSink(), spliterator).get();
        }

 makeSink()就是上面所说的返回了一个包装了终止操作的Sink。get()即为最终结果

# AbstractPipeline.wrapAndCopyInto:将此PipelineHelper描述的管道阶段应用于提供的Spliterator并将结果发送到提供的Sink。也就是让每个阶段处理数据,并将结果发送到终止操作的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;
    }

sink:终止操作的Sink。当前的this为map映射返回的Stream。返回值也是终止操作的Sink

# AbstractPipeline.wrapSink:包装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;
    }

sink一开始的值为终止操作的Sink 

for:

 1.将p初始为当前的stream(map映射返回的Stream)。当前stream的深度大于0,执行第2步,否则退出循环

 2.执行p的opWrapSink方法(这里以map映射返回的Stream为例)

    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) {
                return new Sink.ChainedReference<P_OUT, R>(sink) {
                    @Override
                    public void accept(P_OUT u) {
                        downstream.accept(mapper.apply(u));
                    }
                };
            }
        };
    }

 map中的opWrapSink方法参数sink在这里就是终止操作的Sink。返回了map对应新的Sink。从下面ChainedReference的构造函数可以看出,该sink的downstream即为终止操作的Sink。

总结:opWrapSink方法就是返回的当前stage的sink,且该sink中的downStream指向了下一个操作的Sink

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

 3.将p指向它的前一个stage,重复第2步,知道深度不大于0,。head的深度为0.所以最终的返回sink就是head的下一stage对应的sink.上例中就是filter对应的sink。

这样就得到了一个代表了流水线上所有操作的Sink(单项链表的结构)

# AbstractPipeline.copyInto

   final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
        Objects.requireNonNull(wrappedSink);

        if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
            //1
            wrappedSink.begin(spliterator.getExactSizeIfKnown());
            spliterator.forEachRemaining(wrappedSink);
            wrappedSink.end();
        }
        else {
            copyIntoWithCancel(wrappedSink, spliterator);
        }
    }

此时的wrappedSink即为wrapSink方法返回的head阶段的下一阶段对应的sink。示例中即为filter对应的Sink。

这里以非短路操作进行说明,也就是代码1处

1.调用wrappedSink的begin方法。(示例代码会依次用于filter.sink.begin-->map.sink.begin-->终止.sink.begin)。有有状态操作的并不一定一次性调用完整个链路上的begin方法,比如sorted。但是一定都会调用,具体调用时机根据具体方法定义。下面是 .collect(Collectors.toList())对应的Sink。可以看出begin方法调用了supplier.get()。supplier为Collectors.toList()返回的Collector中的supplier,从下方Collectors.toList() 源码中可以看出,supplier.get()会创建一个ArrayList。

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

Collectors.toList() 

   public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    }

 2.调用spliterator.forEachRemaining(wrappedSink)。spliterator.forEachRemaining会循环源数据。依次调用wrappedSink的accept方法。(示例代码会依次用于filter.sink.accept-->map.sink.accept-->终止.sink.accept)。从上述终止Sink的accept方法可以看出,该方法会将前一阶段处理后的值添加到集合中(accumulator对应的就是List::add)。

3.wrappedSink.end()。示例中都是调用Sink接口中默认的空实现。

问题四:操作结果在哪?

现在在回过头去看下# AbstractPipeline.wrapAndCopyInto返回了终止操作对应的Sink。最终的结果调用的就是终止Sink的get方法。示例中ReducingSink extends Box,最终的结果就是调用父类Box的get方法。返回的就是一开始begin方法supplier创建的集合。该集合中已经添加了需要返回的所有数据。

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值