Collection如何转成stream以及Spliterator对其操作的实现

Collection如何转成stream

在java 1.8中,Collection新增了一个default方法stream(),他可以将集合转换成流,那么这节我将会深入源码看看具体过程是如何。

Collection中的操作

首先查看Collection中的stream方法

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}
复制代码

该方法调用了StreamSupport.stream方法,在前面我分析了StreamSupport的这个方法的执行步骤,该方法接收一个Spliterator参数以及是否是并发的判断,主要的数据保存在Spliterator中。此时我们再看看spliterator()方法

@Override
default Spliterator<E> spliterator() {
    return Spliterators.spliterator(this, 0);
}
复制代码

这是一个重写方法,它的父类方法如下:

default Spliterator<T> spliterator() {
    return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
复制代码

该方法调用Spliterators.spliteratorUnknownSize()方法,并传入一个迭代器和特征值,而Collection中重写的方法调用的是Spliterators.spliterator(),传递的是一个Collection对象和特征值.
我们直接看Spliterator.spliterator()方法

public static <T> Spliterator<T> spliterator(Collection<? extends T> c,
                                                 int characteristics) {
    return new IteratorSpliterator<>(Objects.requireNonNull(c),
                                     characteristics);
}
复制代码

它先判断传入的集合是否为空,然后返回一个IteratorSpliterator对象,接着我们看看这个对象的构造方法

public IteratorSpliterator(Collection<? extends T> collection, int characteristics) {
    this.collection = collection;
    this.it = null;
    this.characteristics = (characteristics & Spliterator.CONCURRENT) == 0
                           ? characteristics | Spliterator.SIZED | Spliterator.SUBSIZED
                           : characteristics;
}
复制代码

该构造方法的作用是根据给定的集合创建一个spliterator。该方法位于Spliterators类下面,根据java8对于这些类-接口的设计模式,该类主要用于对Spliterator的具体静态实现。因此我们直接看IteratorSpliterator类的继承关系

文档上对于它的描述为:

使用给定迭代器进行元素操作的Spliterator。 同时实现分裂器trySplit()以允许有限的并行性。

可见,该类主要用于迭代器生成Spliterator,并且允许一定的并发性。 既然生成了Spliterator对象,那么就可以直接调用StreamSupport.stream(spliterator: Spliterator, parallel : boolean)方法创建Stream了。

Spliterator对其操作的实现

这节主要介绍上一节的迭代器Spliterator的具体实现,让我们看看jdk作者们是如何对有序的集合进行并发操作的。

首先我们看看IteratorSpliterator新定义的参数

static final int BATCH_UNIT = 1 << 10;  // batch array size increment
static final int MAX_BATCH = 1 << 25;  // max batch array size;
private final Collection<? extends T> collection; // null OK
private Iterator<? extends T> it;
private final int characteristics;
private long est;             // size estimate
private int batch;            // batch size for splits
复制代码

其中我们需要关注的是batch参数,因为他就是涉及到如何拆分,该参数的作用是确定拆分的大小。而这个characteristics便是它的特征值,这个值直接关系到生成stream时候限定的各个阶段能进行的操作。 对特征值的计算如下:

characteristics = (characteristics & Spliterator.CONCURRENT) == 0
                                   ? characteristics | Spliterator.SIZED | Spliterator.SUBSIZED
                                   : characteristics;
复制代码

其中的Spliterator.CONCURRENT参数表示可以在没有外部同步的情况下由多个线程安全地同时修改元素源(允许添加,替换和/或删除)的特征值。这里的判断为:如果给定的特征值不允许并行,那么该特征值允许按顺序遍历(Spliterator.SIZED)并且该数据由Spliterator产生(Spliterator.SUBSIZED),否则直接保持原来的特征值。

关于特征值的计算我之前一直没懂为什么都是用 | 和 & 这两个运算符号,直到现在我才发现,这是为了在有限的位数中尽可能地多保存信息。而|是为了增加信息,&是为了判断是否存在该信息。比如:

有一个List,对于它的操作由一组特征值限定。

  • 0b001 - 允许增加
  • 0b010 - 允许删除
  • 0b100 - 允许修改

当这个List的特征值为0b011时:
    0b001 & 0b011的结果为1,0b011 & 0b010的结果为1,0b011 & 0b100的结果为0,则该List允许增加、删除,但是不允许修改。
那么如何让他允许修改呢?
    只要将特征值修改为0b011 | 0b100即可,该结果为7转换成二进制则为0b111.

接着查看重头戏,IteratorSpliterator对trySplit的实现:

@Override
public Spliterator<T> trySplit() {
    Iterator<? extends T> i;
    long s;
    if ((i = it) == null) {
        i = it = collection.iterator();
        s = est = (long) collection.size();
    }
    else
        s = est;
    if (s > 1 && i.hasNext()) {
        int n = batch + BATCH_UNIT;
        if (n > s)
            n = (int) s;
        if (n > MAX_BATCH)
            n = MAX_BATCH;
        Object[] a = new Object[n];
        int j = 0;
        do { a[j] = i.next(); } while (++j < n && i.hasNext());
        batch = j;
        if (est != Long.MAX_VALUE)
            est -= j;
        return new ArraySpliterator<>(a, 0, j, characteristics);
    }
    return null;
}
复制代码

这里的操作我们对应上面的构造方法,我们的流程是使用集合来构造Spliterator的因此,表示迭代器的it为null。因此会执行第一个条件判断

if ((i = it) == null) {
    i = it = collection.iterator();
    s = est = (long) collection.size();
}
复制代码

此时会将集合中的迭代器赋值给i和it,集合中的元素个数赋值给s和est。否则就将迭代器中的元素个数赋值给s。

当元素的个数大于1的时候int n的值为拆分的批量大小与批量大小数组增量(1 << 10 = 2 ^ 10 = 1024)的和。如果该值大于元素的个数,则将n赋值为元素个数,如果该值大于最大批量数组的大小(1 << 25 = 2 ^ 25 = 0x2000000)则将n赋值为最大批量数组的大小。然后创建Object[]数组,数组大小为n,将迭代器中的元素添加进数组,然后将拆分的批量大小(batch)赋值为循环赋值次数,如果最开始迭代器中的元素个数不等于Long的最大值(0x7fffffffffffffffL),则est减去循环次数,即est为剩下的元素的个数。将赋值后的Object[]作为参数创建ArraySpliterator对象。

官方给这段代码的注释为:

分成算术增加批量大小的数组。 如果每个元素的Consumer操作比将它们转移到数组中更昂贵,那么这只会提高并行性能。 在分割大小中使用算术级数提供了开销与并行性边界,这些边界不会特别有利于或惩罚轻量级与重量级元素操作的情况,跨越#elements与#cores的组合,无论是否已知。 我们生成O(sqrt(#elements))分割,允许O(sqrt(#cores))潜在的加速。

而对于元素个数的边界为批量大小数组的增量BATCH_UNIT(1 << 10 = 1024).即将1024个元素分成一组进行并发操作.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值