浅析Java8 Stream原理


上一篇文章中大体的介绍了Stream的概念和基本API的使用,Stream用起来的确非常的爽。这一篇文章将会讲述Stream的底层实现原理。

操作符的分类

Stream中的操作可以分为两大类:中间操作与结束操作,中间操作只是对操作进行了记录,只有结束操作才会触发实际的计算(即惰性求值),这也是Stream在迭代大集合时高效的原因之一。
中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。
结束操作又可以分为短路与非短路操作,这个应该很好理解,前者是指遇到某些符合条件的元素就可以得到最终结果;而后者是指必须处理所有元素才能得到最终结果。

流水线的结构

Stream的主要接口类的关系如下图:
stream

  • BaseStream规定了流的基本接口
  • Stream中定义了map、filter、flatmap等用户关注的常用操作;
  • Int~ Long~ Double~是针对于基本类型的特化 方法与Stream中大致对应,当然也有一些差别
  • BaseStream Stream IntStream LongStream DoubleStream 组建了Java的流体系根基
  • PipelineHelper主要用于Stream执行过程中相关结构的构建ReferencePipeline和AbstractPipeline
  • AbstractPipeline是流水线的核心抽象类,用于构建和管理流水线。它的实现类就是流水线的节点。
  • Head、StatelessOp、StatefulOp为ReferencePipeline中的内部类,[Int | Long | Double]Pipeline 内部也都是定义了这三个内部类。

IntPipeline, LongPipeline, DoublePipeline这三个类专门为三种基本类型而定制的,Int、long、double进行了优化,主要用于频繁的拆装箱。三者跟ReferencePipeline是并列关系,
StatefulOp、StatelessOp分别对应有状态和无状态中间操作。,很多Stream操作会需要一个回调函数(Lambda表达式),一个完整的操作是<数据来源,操作,回调函数>构成的三元组。

Stream中使用Stage的概念来描述一个完整的操作,将具有先后顺序的各个Stage连到一起,就构成了整个流水线。

AbstractPipeline

前面说到AbstractPipeline是流水线的核心。AbstractPipeline中定义了三个AbstractPipeline类型的变量:sourceStage(源阶段),previousStage(上游pipeline,前一阶段),nextStage(下一阶段)。

/**
 * Backlink to the head of the pipeline chain (self if this is the source stage).
 */
private final AbstractPipeline sourceStage;
/**
* The "upstream" pipeline, or null if this is the source stage.
*/
private final AbstractPipeline previousStage;
/**
 * The next stage in the pipeline, or null if this is the last stage.
 * Effectively final at the point of linking to the next pipeline.
 */
private AbstractPipeline nextStage;

它的直接实现类为ReferencePipeline,而Head 、StatefulOp 、StatelessOp又继承了ReferencePipeline类。因此Head StatefulOp StatelessOp 他们本身也是AbstractPipeline类型的。
每一个stage就是一个AbstractPipeline的实例,注意一开始笔者和Netty中的Pipelie做以类比,其实根本不是一回事。这里的每一个pipeline都是一个节点。

Head用于表示第一个Stage,也就是source stage,调用诸如Collection.stream()方法产生的Stage,很显然这个Stage里不包含任何操作;StatelessOp和StatefulOp分别表示无状态和有状态的Stage,对应于无状态和有状态的中间操作。
注意:终结操作不会添加节点。
stream
Collection.stream()方法得到Head也就是stage0,紧接着调用一系列的中间操作,不断产生新的stage。这些stage对象以双向链表的形式组织在一起,构成整个流水线。
由于每个Stage都记录了前一个Stage和本次的操作以及回调函数,依靠这种结构就能建立起对数据源的所有操作。

下面分析Head节点的构建。

Stream的生成源码分析

不管是Collection中调用StreamSupport.stream()还是Stream的of方法 都是调用了StreamSupport.stream方法。以Collecton.stream()为例

default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

这是一个接口方法的默认实现,第一个参数是获取一个 Spliterator的实例,它表示从数据源中获取元素的方式。相当于升级版的Iterator。第二个参数是是否并行。
继续进入StreamSupport类中:

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

看到这里和前面说的pipeline有点联系了,此处构造Head节点。我们看一下Head它的构造函数链:

Head(Spliterator<?> source,
  int sourceFlags, boolean parallel) {
  super(source, sourceFlags, parallel);//source - 描述流的源 sourceFlags - 流的来原标志 parallel - 是否为并行流
}
ReferencePipeline(Spliterator<?> source,
              int sourceFlags, boolean parallel) {
super(source, sourceFlags, parallel);
}
AbstractPipeline(Spliterator<?> source,
                 int sourceFlags, boolean parallel) {
    this.previousStage = null; // 上游管道,第一次创建流则为null
    this.sourceSpliterator = source; // 源分裂器。仅对头管道有效。
    this.sourceStage = this; // 反向链接到管道链的头部(如果这是源阶段,则为它本身)
    this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK; //此管道对象中表示的中间操作的操作标志。
    // The following is an optimization of:
    //源以及所有操作的组合源的操作标志,包括此管道对象表示的操作。在评估管道准备时有效。
    this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
    this.depth = 0; //此管道对象与流源(如果是顺序)之间的中间操作数,或之前的有状态(如果并行)。在评估管道准备时有效。
    this.parallel = parallel;//如果管道是并行的,则为真,否则管道是顺序的;仅对源阶段有效.
}

第一个构造函数对应的类为:static class Head<E_IN, E_OUT> extends ReferencePipeline<E_IN, E_OUT>,注意这一有一个细节:范型的命名,<E_IN>上游源中的元素类型 <E_OUT>此阶段生成的元素类型ReferencePipeline这个类由继承了AbstractPipeline<P_IN, P_OUT, Stream<P_OUT>>,同时实现了Stream<P_OUT>的各种操作符。至此头结点已经构造完成。

添加中间操作

看看filter的代码:

@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) {
            return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                @Override
                public void begin(long size) {
                    downstream.begin(-1);
                }

                @Override
                public void accept(P_OUT u) {
                    if (predicate.test(u))
                        downstream.accept(u);
                }
            };
        }
    };
}

可以看到返回了一个无状态stage,也是一个AbstractPipeline、stream,即是流水线的一个阶段。同时还实现了AbstractPipeline定义的opWrapSink方法。
看到这里似乎有些困惑,一片Override方法到底什么意思?

Sink
Stream中将操作抽象化为stage 每个stage 也就是一个AbstractPipeline,每个stage 相当于一个双向链表的节点 ,每个节点都保存Head然后保存着上一个和下一个节点。这个双向链表就构成了整个流水线。

但是似乎有一个最重要的东西没有提到,那么就是每一个阶段的操作。如何将多个操作叠加到一起呢?
你可能会觉得这很简单,只需要从流水线的head开始依次执行每一步的操作(包括回调函数)就行了。
这听起来似乎是可行的,但是你忽略了前面的Stage并不知道后面Stage到底执行了哪种操作,以及回调函数是哪种形式。换句话说,只有当前Stage本身才知道该如何执行自己包含的动作。这就需要有某种协议来协调相邻Stage之间的调用关系。这就是Sink接口存在的意义。
创建的Sink.ChainedReference类构造方法如下:

public ChainedReference(Sink<? super E_OUT> downstream) {
           this.downstream = Objects.requireNonNull(downstream);
       }

Sink接口相当于对操作(我们实现的函数式接口)封装了一层,每一个阶段只需要调用自己的Sink的accept方法,accept内部只要调用下一个阶段的accept
不需要知道下一个极端的操作类型是什么。

当然Sink添加进行一些扩展功能,比如:begin表示开始遍历元素前的方法,相当于AOP。end表示元素遍历结束之后,cancellationRequested表示是否可以结束操作,可以让短路操作尽早结束。

实际上Stream 操作符内部实现的的本质,就是实现Sink的这四个接口方法。
Sink接口的方法几乎都是按照这种[处理->转发]的模型实现,如上面的accept

1. 使用当前Sink包装的回调函数处理u
2. 将处理结果传递给流水线下游的Sink

当添加了中间操作符之后的链表结构如下,Head中没有任何操作,因此也没有实现Sink。
stream
在这里产生一个疑问,重写的opWrapSink什么时候被调用?以及Sink的accept什么时候被调用?正如图中所示,我们只是重写了opWrapSink方法,保存在每一个节点中。每一个Sink都是独立的,它的downstream还没有赋值。

万事俱备,只欠东风

何为东风?显然是终结操作符,以forEach为例,实现在ReferencePipeline中:

@Override
public void forEach(Consumer<? super P_OUT> action) {
    evaluate(ForEachOps.makeRef(action, false));
}

ForEachOps是用户创建TerminalOp实例的工厂类。TerminalOp是终止操作最顶层的一个接口。TerminalOp接口的实现类有ForEachOp, ReduceOp,FindOp, MatchOp。
先看ForEachOps.makeRef()方法:

public static <T> TerminalOp<T, Void> makeRef(Consumer<? super T> action,
                                              boolean ordered) {
    Objects.requireNonNull(action);
    return new ForEachOp.OfRef<>(action, ordered);
}

OfRef是引用流的默认实现类,这里新建了一个OfRef的实例,构造方法如下:

OfRef(Consumer<? super T> consumer, boolean ordered) {
    super(ordered);// 父类ForEachOp,参数表述遍历是否有序,前面传入的false
    this.consumer = consumer;
}

将我们实现的Consumer函数式接口赋值给成员变量。回到evaluate方法:

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

    return isParallel()
           ? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))
           : terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
}

跟到串行流的实现,实现在ForEachOp中:

@Override
public <S> Void evaluateSequential(PipelineHelper<T> helper,
                                   Spliterator<S> spliterator) {
    return helper.wrapAndCopyInto(this, spliterator).get();
}

数PipelineHelper类型其实是AbstractPipeline的父类,而AbstractPipeline又是ReferencePipeline的父类。再跟进helper.wrapAndCopyInto方法,是现在AbstractPipeline中:

@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;
}
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> 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;
}

可以看到通过ReferencePipeline的双向链表,从最后一个操作(也就是终止操作)往前遍历,将所有的操作都串联起来,最终返回一个指向第一个操作的Sink引用。

这里有一个细节问题:
final <P_IN, S extends Sink<E_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator)方法的第一个入参为泛型,几Sink的子类。对应的有Sink.ofLong、Sink.ofInt等。并且这个参数也属于TerminalOp类型,说白了终结操作符最终也被包装成了Sink类型,这一切都通了,最后一个中间操作的downStream是终结操作符。

真的要执行了
回到copyInto方法,wrapSink返回了Head后第一个中间操作的包装Sink,继续看copyInto的实现:

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

第一个参数我们知道Head后的节点,spliterator可以看作数据源。逻辑分为短路操作和非短路操作,如果有短路操作就会执行下面的copyIntoWithCancel方法,否则指向上面的逻辑,这里我们非常熟悉啊,begin、accept、end先后串联执行。

短路流在执行遍历的时候会调用Sink封装的cancellationRequested方法,如果返回出就不会进行后面的操作,过程稍微比非短路复杂一点,但是原理大致相同。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
浅析java常用的设计模式(doc 23页) 1、工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即 可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修 改。如:如何创建及如何向客户端提供。   2、建造模式:将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程 生成具有不同的内部表象的产品对象。建造模式使得产品内部表象可以独立的变化,客 户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。   3、工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交 给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接 触哪一个产品类应当被实例化这种细节。   4、原始模型模式:通过给出一个原型对象来指明所要创建的对象的类型,然后用复 制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少 产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等 级结构。缺点是每一个类都必须配备一个克隆方法。   5、单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统 提供这个实例单例模式。单例模式只应在有真正的"单一实例"的需求时才可使用。   6、适配器(变压器)模式:把一个类的接口变换成客户端所期待的另一种接口,从 而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参 数返还一个合适的实例给客户端。   7、桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们 之间的强关联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合/聚 合关系而不是继承关系,从而使两者可以独立的变化。   8、合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。 合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表 示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同 等看待。   9、装饰模式:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个 替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可以再动态 的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。 一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另 一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象, 代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与 真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象 的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代 为创建并传入。   13、责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接   起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。 客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下 动态的重新组织链和分配责任。处理者有两个选择:承担责任或者把责任推给下家。一 个请求可以最终不被任何接收端对象所接受。   14、命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出 命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和 发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请 求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令 的撤消。   15、解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并 同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。解释器模 式将描述怎样在有了一个简单的文法后,使用模式设计解释这些语句。在解释器模式里 面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个 代表文法的命令类的等级结构,也就是一系列的组合规则。每一个命令对象都有一个解 释方法,代表对命令对象的解释。命令对象的等级结构中的对象的任何排列组合都是一 个语言。   16、迭代子模式:迭代子模式可以顺序访问一个聚集中的元素而不必暴露聚集的内 部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容 器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。迭 代子模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以上的迭代子对象, 每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色变化。   17、调停者模式:调停者模式包装了一系列对象相互作用的方式,使得

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值