java8源码学习-java.util.stream:我Stream诞生啦

这会是一系列的文章,用来记录我学习jdk8-Stream源码的学习笔记。我会通过直接查看jdk源码以及jdk8文档来进行学习,其中会有我自己的理解,可能没有用到专业术语或者有错误,欢迎捉虫。

jdk8-stream简介

根据oracle官方对jdk8新特性的介绍我们可以看到关于java.util.stream的简介:

  • 新的 java.util.stream 包中的类提供了一个 Stream API,支持对元素流进行函数式操作。Stream API 集成在 Collections API 中,可以对集合进行批量操作,例如顺序或并行的 map-reduce 转换。 来自:oracle-jdk8

java.util.stream目录结构

目录结构如下:

官方文档对他的目录结构如下:

从结构我们可以发现,java.util.stream下面的主要类就是StreamSupport,我会从这个类开始一步一步学习。

开始学习

让我们先从jdk文档看起,根据文档对StreamSupport的介绍

  • public final class StreamSupport extends Object
  • Low-level utility methods for creating and manipulating streams.
  • This class is mostly for library writers presenting stream views of data structures; most static stream methods intended for end users are in the various Stream classes.

它除了继承Object类之外没有继承或实现任何类。 主要用于创建和操作流的低级实用程序方法。提供数据结构的流视图。 针对最终用户的大多数静态流方法都在各种Stream类中。

从介绍我们发现,StreamSupport主要用于创建和操作流的低级程序方法。而针对用户的大部门流的操作方法都在各种Stream类中。我们可以发现在文档中显示的类除了StreamSupport只有一个Collectors类,那么这里我猜测流的大部分操作方法都用default修饰并在interface中实现。这里我先不直接看IntStream之类的类,还是继续查看StreamSupport。

构造函数部分:

private StreamSupport() {}
复制代码

这里很有意思,它的构造函数用private修饰,当用private修饰构造函数的时候改函数便无法被new实例化。关于这方面我查找了《thinking in java》的相关内容,他对于这部分的介绍如下:

可能想控制如何创建对象,并阻止别人访问某个特定构造器(或全部构造器)。他还有另一个效果:既然默认构造器是唯一的构造器,并且它是private的,那么它将阻碍对此类的继承。

而源码上的注释也表明了这点:

Suppresses default constructor, ensuring non-instantiability.

<T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel)

文档的介绍如下:

Creates a new sequential or parallel Stream from a Spliterator.

从Spliterator创建新的顺序或并行流。

源码:

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

对于它的参数,通过文档可以获知:

spliterator - a Spliterator describing the stream elements
parallel - if true then the returned stream is a parallel stream; if false the returned stream is a sequential stream.

一个是描述流的元素,一个是判断是否是并行流

他会先进行null检查

Objects.requireNonNull(spliterator);
复制代码

该方法是Objects类的静态方法用于判断传入参数是否为空

public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }
复制代码

然后返回一个ReferencePipeline.Head对象引用。 查看这个类的构造器:

Head(Spliterator<?> source,
             int sourceFlags, boolean parallel) {
            super(source, sourceFlags, parallel);
        }
复制代码

该构造器接收三个参数:

  • source - 描述流的源
  • sourceFlags - 流源的原标志
  • parallel - 是否为并行流

而这个类继承自ReferencePipeline<E_IN, E_OUT>

static class Head<E_IN, E_OUT> extends ReferencePipeline<E_IN, E_OUT> {
    /** 代码省略 */
}
复制代码

它的连个范型和我平时接触的T、E完全不一样,通过查看源码我发现这样的命名是为了更方便理解两个范型元素的作用

  • @param <E_IN>上游源中的元素类型
  • @param <E_OUT>此阶段生成的元素类型

也就是说它是接收一个源再返回一个源

那么我们继续通过super查看父类的构造器:

ReferencePipeline(Spliterator<?> source,
                      int sourceFlags, boolean parallel) {
        super(source, sourceFlags, parallel);
    }
复制代码

emmmm,这个父类ReferencePipeline的命名很有意思:"参考管道",我明明想要创建流,结果这给我命名了一个Pipeline.是不是说流就是一种特殊的管道呢?这里我保持疑问,因为现在还不知道流如何诞生。

ReferencePipeline这个类由继承了AbstractPipeline<P_IN, P_OUT, Stream<P_OUT>>以及实现了Stream<P_OUT>。这里我还是先不看它的继承结构,继续通过super查看父类构造器:

 AbstractPipeline(Spliterator<?> source,
                     int sourceFlags, boolean parallel) {
        this.previousStage = null;
        this.sourceSpliterator = source;
        this.sourceStage = this;
        this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;
        this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
        this.depth = 0;
        this.parallel = parallel;
    }
复制代码

这里有很多参数,我一个一个找出来:

  • AbstractPipeline previousStage - The "upstream" pipeline, or null if this is the source stage.
  • 上游管道,如果是原阶段(也就是第一次创建流)则为null
  • Spliterator<?> sourceSpliterator - The source spliterator. Only valid for the head pipeline. Before the pipeline is consumed if non-null then {@code sourceSupplier} must be null. After the pipeline is consumed if non-null then is set to null.
  • 源分裂器。仅对头管道有效。如果非null,则在使用管道之前,{@code sourceSupplier}必须为null并且在在使用管道之后将其设置为null。
  • AbstractPipeline sourceStage - Backlink to the head of the pipeline chain (self if this is the source stage).
  • 反向链接到管道链的头部(如果这是源阶段,则为它本身)。
  • int sourceOrOpFlags - The operation flags for the intermediate operation represented by this pipeline object.
  • 此管道对象中表示的中间操作的操作标志。
  • int combinedFlags - The combined source and operation flags for the source and all operations up to and including the operation represented by this pipeline object. Valid at the point of pipeline preparation for evaluation.
  • 源以及所有操作的组合源的操作标志,包括此管道对象表示的操作。在评估管道准备时有效。
  • int depth - The number of intermediate operations between this pipeline object and the stream source if sequential, or the previous stateful if parallel. Valid at the point of pipeline preparation for evaluation.
  • 此管道对象与流源(如果是顺序)之间的中间操作数,或之前的有状态(如果并行)。在评估管道准备时有效。
  • boolean parallel - True if pipeline is parallel, otherwise the pipeline is sequential; only valid for the source stage
  • 如果管道是并行的,则为真,否则管道是顺序的;仅对源阶段有效.

在我们调用的时候sourceSupplier没有没赋值为null,则源分裂器被赋值为source源流. 由于我是第一次创建,则它的上游管道为null同时反向链接管道的头部也为它本身,用this赋值;而对源几次所有操作的组合源的操作标志为"源的原标志 & 源流标志的位掩码。"关于流源标志的位掩码我查看它的定义为:

static final int STREAM_MASK = createMask(Type.STREAM);
复制代码

他根据Type.STREAM由createMask生成,该方法如下:

private static int createMask(Type t) {
        int mask = 0;
        for (StreamOpFlag flag : StreamOpFlag.values()) {
            mask |= flag.maskTable.get(t) << flag.bitPosition;
        }
        return mask;
    }
复制代码

他会通过遍历获取StreamOpFlag类型的对象然后调用它的maskTable.get(Type)方法, 我先查看StreamOpFlag.values()看看遍历的对象有哪些

enum StreamOpFlag {
    DISTINCT(0, set(Type.SPLITERATOR).set(Type.STREAM).setAndClear(Type.OP)),
    SORTED(1, set(Type.SPLITERATOR).set(Type.STREAM).setAndClear(Type.OP)),
    ORDERED(2, set(Type.SPLITERATOR).set(Type.STREAM).setAndClear(Type.OP).clear(Type.TERMINAL_OP).clear(Type.UPSTREAM_TERMINAL_OP)),
    SIZED(3, set(Type.SPLITERATOR).set(Type.STREAM).clear(Type.OP)),
    SHORT_CIRCUIT(12, set(Type.OP).set(Type.TERMINAL_OP));

    /** other code */
}
复制代码

源码中对于该类的注释为:

Flags corresponding to characteristics of streams and operations. Flags are utilized by the stream framework to control, specialize or optimize computation. Stream flags may be used to describe characteristics of several different entities associated with streams: stream sources, intermediate operations, and terminal operations. Not all stream flags are meaningful for all entities; the following table summarizes which flags are meaningful in what contexts:

对应于流和操作的特征的标志。流框架使用标志来控制,专门化或优化计算。 流标志可用于描述与流相关联的若干不同实体的特征:流源、中间操作和终端操作。并非所有流标志对所有实体都有意义;下表总结了哪些标志在哪些上下文中有意义: 表1.0

在上表中,“PCI”表示“可以保存,清除或注入”; “PC”表示“可以保留或清除”,“PI”表示“可以保留或注入”,“N”表示“无效”。

回到源码部分,我去其中一个来看

DISTINCT(0, set(Type.SPLITERATOR).set(Type.STREAM).setAndClear(Type.OP)),
复制代码

它传入两个参数,根据StreamOpFlag的构造器可以看到:

private StreamOpFlag(int position, MaskBuilder maskBuilder) {
        this.maskTable = maskBuilder.build();
        // Two bits per flag
        position *= 2;
        this.bitPosition = position;
        this.set = SET_BITS << position;
        this.clear = CLEAR_BITS << position;
        this.preserve = PRESERVE_BITS << position;
    }
复制代码

它接收一个位置特征值和匹配的掩码生成器。

每个特征在位集中占用2位以适应保留、清除和设置/注入信息。 这适用于流标志,中间/终端操作标志以及组合的流和操作标志。 即使前者每个特性仅需要1位信息,但在组合标志以对齐集合和注入位时更有效。

特征属于某些类型,具体的就是枚举的类型。类型的位掩码按照下表构造:

表1.1

TypeDISTINCTSORTEDORDEREDSIZEDSHORT_CIRCUIT
SPLITERATOR0101010100
STREAM0101010100
OP1111111001
TERMINAL_OP0000100001
UPSTREAM_TERMINAL_OP0000100000
  • 01 = set/inject 设置/注入
  • 10 = clear 清除
  • 11 = preserve 保留

Type列对应的是enum的枚举。那么我们选取的该行代码的意思是:

位置特征为0,匹配的掩码生成器 01(SPLITERATOR, DISTINCT) 01(STREAM, DISTINCT) 11(OP, DISTINCT)

此时查看MaskBuilder类


private static final int SET_BITS = 0b01;

private static final int CLEAR_BITS = 0b10;

private static final int PRESERVE_BITS = 0b11;
    
private static class MaskBuilder {
        final Map<Type, Integer> map;

        MaskBuilder(Map<Type, Integer> map) {
            this.map = map;
        }

        MaskBuilder mask(Type t, Integer i) {
            map.put(t, i);
            return this;
        }

        MaskBuilder set(Type t) {
            return mask(t, SET_BITS);
        }

        MaskBuilder clear(Type t) {
            return mask(t, CLEAR_BITS);
        }

        MaskBuilder setAndClear(Type t) {
            return mask(t, PRESERVE_BITS);
        }

        Map<Type, Integer> build() {
            for (Type t : Type.values()) {
                map.putIfAbsent(t, 0b00);
            }
            return map;
        }
    }
复制代码

该类通过set(),clear(), setAndClear() 这三个方法给特征码对应的类型创建相应的key-value结构。这个例子中MaskBuilder最终的结果是:

{
    Type.SPLITERATOR : 0b01,
    Type.STREAM : 0b01,
    Type.Op : 0b11
}
复制代码

最终调用构造函数得构造函数的参数如下:

{
    maskTable : {
        Type.SPLITERATOR : 0b01,
        Type.STREAM : 0b01,
        Type.Op : 0b11,
        TERMINAL_OP : 0b00,
        UPSTREAM_TERMINAL_OP : 0b00
    }, 
    position : position *= 2,
    bitPosition : position,
    set : 0b01 << position,
    clear : 0b01 << position,
    preserve : 0b11 << position
}
复制代码

此时回到createMask方法,它会对maskTable中的元素进行遍历然后使用<<转换符使得对应的掩码左移position位然后用|=赋值给mask。这里我找资料才理解|=运算:

a|=b的意思就是把a和b按位或然后赋值给a 按位或的意思就是先把a和b都换成2进制,然后用或操作,相当于a=a|b 。这里我直接举例子:

0 |= 1 : 先转换成二进制:0000 |= 0001,那么对应位数是0 - 1,此时 0 | 1 = 1,所以最终结果是0001;
1 |= 3 : 转换成二进制 : 0001 |= 0011,此时各个位数对应关系: 1 | 1 = 1, 0 | 1 = 1;最终结果位0011;
看来相关的基础还是不怎么熟练。

那么createMask这个方法的最终作用就出来了:

它根据传入的Type对应表1.1得到对应的流掩码,再根据position对掩码进行左移操作,并将得到的所有结果按位或。

最后我们回到sourceOrOpFlags的赋值:

获取流的掩码和源流的原标志的按位与的结果

接着查看

this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
复制代码

对于该参数的解释在上面有

源以及所有操作的组合源的操作标志,包括此管道对象表示的操作。在评估管道准备时有效。

那么它是如何表示操作的呢?
我发现它的StreamOpFlag.INITIAL_OPS_VALUE;参数在源码中是两个参数的按位或操作结果

int INITIAL_OPS_VALUE = FLAG_MASK_IS | FLAG_MASK_NOT;
复制代码

源码中对他的解释为

要与管道中第一个流的流标志组合的初始值。

而这两个参数

    private static final int FLAG_MASK_IS = STREAM_MASK;
    private static final int FLAG_MASK_NOT = STREAM_MASK << 1;
复制代码

他们一个是设置流标志的位置掩码,一个是清理流标志的位置掩码。而这些操作的位置掩码实际上都是依据STREAM_MASK这个源流标志的位掩码基础上进行位移计算得到的。

最终对于combinedFlags的赋值:

此管道中表示中间操作的值左移一位后的按位否定与管道中第一个流标志组合成的初始值的按位与结果

然后是depth变量:

此管道对象与流源之间的中间操作数(如果是顺序的),或之前的有状态if parallel.Valid在管道准备评估时。

此时对于AbstractPipeline的构造器初始化我已经有了一个大致的了解

对于流管道头部的构造函数:它着重的值有两个,一个是sourceOrOpFlags表示流的一系列中间操作。一个是combinedFlags表示流以及管道的所有操作(这里我有点不理解: 流管道的所有操作与初始值之后的按位与结果这两个具体是如何分工的?)

那么到这里对于调用到该方法之后的一系列操作我有了一个初步的认知

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

等等,还差了一步:对于流源特征位的获取

StreamOpFlag.fromCharacteristics(spliterator),
复制代码

该方法如下:

static int fromCharacteristics(Spliterator<?> spliterator) {
        int characteristics = spliterator.characteristics();
        if ((characteristics & Spliterator.SORTED) != 0 && spliterator.getComparator() != null) {
            // Do not propagate the SORTED characteristic if it does not correspond
            // to a natural sort order
            return characteristics & SPLITERATOR_CHARACTERISTICS_MASK & ~Spliterator.SORTED;
        }
        else {
            return characteristics & SPLITERATOR_CHARACTERISTICS_MASK;
        }
    }
复制代码

这里先获取此Spliterator及其元素的一组特征,并且与Spliterator.SORTED(0x00000004)进行按位与操作如果它的值不为0且这个Spliterator的来源不为null,则将Spliterator与分裂器的位掩码还有遵循顺序定义的特征值的按位否定的结果进行按位与操作。else的话就不添加顺序特征值。对此,代码上的解释为:

如果spliterator自然是{@code SORTED}(关联的{@code Comparator}是{@code null}),那么特性将被转换为{@link #SORTED}标志,否则特征不会被转换。
也就是说,特征值最终都是要添加一个转换标志的。

十分简短的总结

先前并没有这样一系列深挖源码,因此许多观点都是想到什么就写什么并且带有我本人的一部分理解。而且是顺着我自己的思路想到什么写什么,因此会有些乱。欢迎大家来捉虫。

对于上述涉及的类,我画出了对应的uml图,我用的是StarUML画的,不知道为什么类的实现关系变成了实线,因此我直接标注:

在Head的实例化过程中,最终参数的赋值为AbstractPipeline进行。

这里我也标注出在实例话过程中涉及到的枚举类型的关系:

对于生成的位掩码,它是按照表1.1生成的各个阶段的操作。那么对于流最终的生成,我个人的理解是:

创建管道对象,由于它是Stream,因此根据表1.1的STREAM获取到它所有操作的位掩码。

对于表1.0的操作,我这里觉得是源spliterator生成新的Stream时候各个阶段的操作,毕竟构造器中有两个参数:previousStage和sourceStage,从这两个参数的介绍我发现流应该是管道组成的环形链表形式。那么它的数据存在哪里以及平常所用的filter,limit操作是如何进行的呢?我下一章再讲。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值