Java Stream原理解析(二)续

前一篇文章关于Stream pipelines还留下两个问题,分别是叠加之后的操作如何执行以及执行后的结果在哪里,今天,小编在这里做一下收尾。

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

Sink完美的封装了Stream的每个步骤,并给出了【处理->转发】的模式来叠加操作。那经过操作记录保存以及操作叠加之后,就来到了最后一步,Stream是如何启动这一连串的叠加操作的?此时,你可能会记得我们在一开始说的结束操作会触发计算。

结束操作之后不能再有别的操作,所以结束操作并不会创建新的Stage。结束操作会创建一个包装了自己操作的Sink,即流水线的最后一个Sink,这个Sink只需要处理数据而不需要将结果转递给下游Sink。从而,结束操作的Sink任务呼之欲出就是作为流水线的出口工作。

在这里,我们在回顾一下上游的Sink是如何找到下游的Sink的,一种可选方案就是在PipelineHelper中设置一个Sink字段,从流水线种找到下游的Stage并访问Sink字段即可。但是Stream的设计者并未这么做,而是设置了一个Sink AbstractPipeline.opWrapSink(int flags, Sink downstream)方法来得到Sink,该方法作用是返回一个新的包含当前Stage代表的操作以及能够将结果传递给downstream的Sink对象。为什么要生成一个新对象而不是返回一个Sink字段?这是因为使用opWrapSink()可以将当前操作与下游Sink(上文中downstream参数)结合成新的Sink。试想只要从最后一个Stage开始,不断的调用上一个Stage的opWrapSink()方法知道最开始(不包括Stage0,因为Stage0代表数据源),就可以得到一个代表流水线上的所有操作Sink,用代码表示如下:

// java.util.stream.AbstractPipeline#wrapSink:513行
// 从下游向上游不断包装Sink。
// 如果最初传入的Sink代表结束操作,函数返回时就可以得到一个代表了流水线上所有操作的Sink。

@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(p.previousStage.combinedFlags, sink);
    }
    return (Sink<P_IN>) sink;
}

得到所有操作的Sink的之后,执行这个Sink就是开始执行整个流水线,执行Sink代码如下:

// java.util.stream.AbstractPipeline#copyInto:476行
// 对spliterator代表的数据执行wrappedSink代表的操作
@Override
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);
    }
}

首先通过调用wrappedSink.begin()方法告诉Sink数据即将到来,然后调用spliterator.forEachRemaining()方法对数据进行迭代(Spliterator是容器的一种迭代器),最后调用wrappedSink.end()方法告知Sink数据处理结束。

在这里读者疑问,为何sorted()这一步end()会分发到map()的begin()、accept()、end()三个里面,这里留一个悬念,待日后更新。

  • 行后的结果在哪里?

当将流水线上的Sink执行Ok之后,用户所需要的结果在哪里?首先说明的是不是所有的Stream结束操作都有返回结果,有些操作只是为了使用其副作用,比如使用Stream.forEach()方法将结果打印出来(事实上,除了打印之外其他场景都应该避免使用副作用),对于需要返回结果的结束操作结果应该存在哪里那?

特别说明:副作用不应该被滥用,也许你会觉得在Stream.forEach()里进行元素手机是不错的选择,就像下面代码,但是,遗憾的是这样使用正确性和效率都无法保证,因为Stream可能会并行执行。大多数副作用的地方可以使用归约操作更安全和有效的完成。

// 错误的收集方式
ArrayList<String> results = new ArrayList<>();
stream.filter(s -> pattern.matcher(s).matches())
      .forEach(s -> results.add(s));  // Unnecessary use of side-effects!// 正确的收集方式
List<String>results =
     stream.filter(s -> pattern.matcher(s).matches())
             .collect(Collectors.toList());  // No side-effects!

对于返回结果的流水线的结果存在哪里,这里要分不同的情况讨论,下面给出了各种有返回结果的Stream结束操作。

返回类型对应的结束操作
booleananyMatch()
allMatch()
noneMatch()
OptionalfindFirst()
findAny()
归约结果reduce()
collect()
数组toArray()

对于表中的返回boolean或者Optional的操作(Optional是存放一个值的容器)的操作,由于值只返回一个,只需要在对应的Sink种记录该值,等到执行结束之后返回即可。

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

对于返回数据的情况,毫无疑问的结果会放在数组当中。但是最终返回数组之前,结果其实是存储在一个叫做Node的数据结构中的,Node是一种多叉树的结构,元素存储在树的叶子当中,并且一个叶子节点可以存放多个元素,这样做就是为了并行执行方便。

对于collect这样的操作是需要拿到最终end产生的结果.end产生的结果在最后一个Sink中,这样的操作最终都会提供一个取出数据的get方法.

// java.util.stream.AbstractPipeline#evaluate(java.util.stream.TerminalOp<E_OUT,R>) 226行

final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
    assert getOutputShape() == terminalOp.inputShape();
    if (linkedOrConsumed)
        thrownew IllegalStateException(MSG_STREAM_LINKED);
    linkedOrConsumed = true;

    return isParallel()
           ? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))
           : terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
}
// java.util.stream.ReduceOps.ReduceOp:684行
private static abstract class ReduceOp<T, R, S extends AccumulatingSink<T, R, S>>
        implements TerminalOp<T, R> {
    @Override
    public <P_IN> R evaluateSequential(PipelineHelper<T> helper,
                                       Spliterator<P_IN> spliterator) {
        return helper.wrapAndCopyInto(makeSink(), spliterator).get();
    }

    @Override
    public <P_IN> R evaluateParallel(PipelineHelper<T> helper,
                                     Spliterator<P_IN> spliterator) {
        returnnew ReduceTask<>(this, helper, spliterator).invoke().get();
    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值