当前位置 博文首页 > 龚厂长的博客:java8 Stream之Spliterator

    龚厂长的博客:java8 Stream之Spliterator

    作者:[db:作者] 时间:2021-07-26 14:49

    Spliterator是java8新增的接口,意思为分割器或者分离器,作用是遍历元素或者将数据源的数据分割成几个部分。
    Spliterator的实现类非常多,这也体现了该接口的重要性。下图Stream中比较重要的几个类的继承体系:
    在这里插入图片描述

    绿框表示实现类,蓝框表示接口。

    因为Integer、Long、Double比较特殊且常用,所以单独提供了Spliterator的实现类,不过原理与类ArraySpliterator都是一样的。
    上图中Spliterator的直接实现类分为四个,主要原因是它们的数据源不同。

    • ArraySpliterator:数据源为数组;
    • IteratorSpliterator:数据源为迭代器;
    • ConcatSpliterator:数据源为Spliterator,该类的作用是将两个Spliterator拼接成一个Spliterator;
    • InfiniteSupplyingSpliterator:数据源为生成函数。

    除了上面这些实现类之外,java8还提供了工具类Spliterators,ArraySpliterator和IteratorSpliterator就是在该类中定义的,类中提供了创建ArraySpliterator对象和IteratorSpliterator对象的方法。
    接下来,本文首先介绍Spliterator接口中各个方法作用及特征值,然后介绍Spliterator与Iterator接口的区别,最后介绍上面这四个实现类的原理。

    一、Spliterator接口

    1、接口方法

    下面是Spliterator接口中的方法:

    public interface Spliterator<T> {
        //尝试读取下一个元素,并使用action对象处理该元素
        //如果元素已经遍历完,返回false
        boolean tryAdvance(Consumer<? super T> action);
    	//遍历数据源中剩余的所有元素
        default void forEachRemaining(Consumer<? super T> action) {
            do { } while (tryAdvance(action));
        }
    	//对元素进行拆分,被拆分出去的元素组成一个新的Spliterator对象,剩余的元素
    	//还在当前Spliterator对象中
        Spliterator<T> trySplit();
    	//计算所有元素的个数,
    	//如果无法计算、计算成本太高或者元素是无限的,那么可以返回Long.MAX_VALUE,
    	//否则返回精确值
        long estimateSize();
    
        //返回确切元素个数,如果无法计算,则返回-1
        default long getExactSizeIfKnown() {
            return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
        }
    	//返回当前Spliterator的特征值
        int characteristics();
    	//检查Spliterator是否有此特征
        default boolean hasCharacteristics(int characteristics) {
            return (characteristics() & characteristics) == characteristics;
        }
    	//返回比较器,当对元素排序时,会使用到比较器
        default Comparator<? super T> getComparator() {
            throw new IllegalStateException();
        }
    }
    

    2、特征值

    Spliterator还提供了一些静态常量,它们是Spliterator的特征值,用于表示该Spliterator是否具有某种特征。

    	//表示数据源中元素的遍历顺序(encounter order)是固定好的,不会发生改变,比如数组,
    	//forEachRemaining()方法也是按照这一顺序遍历元素
    	//代码注释中称这一顺序为encounter order,
    	//我理解是当前元素的前一个元素和后一个元素在数据源中就已经定义好了,无论遍历还是拆分,它们之间的顺序是不会发生改变的。
        public static final int ORDERED    = 0x00000010;
    
    	//表示数据源中的元素都是不相等的,
    	//也就是任意两个元素下面表达式都返回true:!x.equals(y)
        public static final int DISTINCT   = 0x00000001;
    
    	//表示数据源中的元素是有序的,
    	//如果具有该特征,getComparator()方法可以返回相关的比较器对象,
    	//如果getComparator()方法返回null,表示是按照自然序排序的
        public static final int SORTED     = 0x00000004;
    
    	//表示元素个数是可以精确计算的
        public static final int SIZED      = 0x00000040;
    
    	//表示不存在为null的元素
        public static final int NONNULL    = 0x00000100;
    
    	//表示在遍历的过程中,元素不可以新增、删除、替换
        public static final int IMMUTABLE  = 0x00000400;
    
        //表示数据源中的元素可以被多线程并行的修改、替换、新增,而不需要外部的同步处理
        public static final int CONCURRENT = 0x00001000;
    
    	//使用trySplit()拆分元素后形成的子Spliterator对象中的元素个数也是可以精确计算的
        public static final int SUBSIZED = 0x00004000;
    

    3、Spliterator与Iterator区别

    两个接口的作用都是遍历元素,不过Spliterator提供的功能更为丰富。
    下面先看一下Iterator接口的方法都有哪些:

    public interface Iterator<E> {
        boolean hasNext();
        E next();
        default void remove() {
            throw new UnsupportedOperationException("remove");
        }
        default void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            while (hasNext())
                action.accept(next());
        }
    }
    

    下面介绍一下两者之间的不同:

    1. 使用Iterator更常用的场景是从数据源中获取元素,然后在外部对元素做更加复杂的加工处理,而Spliterator只能使用Consumer处理,无法直接得到数据源中的元素,只能由内部遍历;
    2. Iterator可以删除数据源中的元素,不过这个依赖于数据源的实现,有些数据源是不支持的;
    3. Spliterator可以得到元素个数,而Iterator不可以;
    4. Spliterator提供了很多特征值,可以直接获知元素的特征;
    5. Spliterator提供了拆分方法,可以在并行流中使用。

    二、实现类原理

    本小节只介绍实现类中trySplit()方法,其他的方法比较简单不做介绍。

    1、ArraySpliterator

    该类的数据源为数组,默认特征值是Spliterator.SIZED | Spliterator.SUBSIZED。
    下面看一下trySplit()方法:

            public Spliterator<T> trySplit() {
            	//index表示当前已经遍历到的位置,如果还没开始遍历,那么index表示起始位置
            	//fence表示最后一个元素的位置+1
                int lo = index, mid = (lo + fence) >>> 1;
                return (lo >= mid)
                       ? null
                       : new ArraySpliterator<>(array, lo, index = mid, characteristics);
            }
    

    从代码中可以很明显的看出,每调用一次trySplit()都是将元素平均的一分为二,将左半部分的元素新建一个ArraySpliterator对象。

    2、IteratorSpliterator

    该类的数据源为迭代器。
    下面看一下trySplit()方法:

            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;//est表示元素个数,可能是一个精确值,也可能是不精确的
                if (s > 1 && i.hasNext()) {
                	//batch 初始值是0
                    int n = batch + BATCH_UNIT;//BATCH_UNIT = 1 << 10=1024
                    if (n > s)
                        n = (int) s;
                    if (n > MAX_BATCH)//MAX_BATCH = 1 << 25
                        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;
            }
    

    从代码可以看出,如果元素足够多,第一次拆分时,将BATCH_UNIT个元素拆分出来,第二次拆分,需要拆分出batch + BATCH_UNIT个元素,也就是2*BATCH_UNIT个元素,第三次拆分也是一样。
    这里要注意,trySplit()返回的是ArraySpliterator,我猜测这可能是为了性能的考虑。

    3、ConcatSpliterator

    该类的数据源为Spliterator。
    ConcatSpliterator是将两个Spliterator对象组合成一个,因此ConcatSpliterator提供了两个属性aSpliterator和bSpliterator,同时使用beforeSplit表示aSpliterator是否已经访问结束,如果为true,表示没有。

            public T_SPLITR trySplit() {
                @SuppressWarnings("unchecked")
                //beforeSplit为true,表示先访问aSpliterator
                T_SPLITR ret = beforeSplit ? aSpliterator : (T_SPLITR) bSpliterator.trySplit();
                beforeSplit = false;
                return ret;
            }
    

    如果beforeSplit为true,那么直接将aSpliterator返回,作为拆分结果,之后的拆分,都是调用bSpliterator.trySplit()完成。

    4、InfiniteSupplyingSpliterator

    InfiniteSupplyingSpliterator是抽象类,它有四个实现类:OfRef、OfDouble、OfInt、OfLong,分别用于处理对象、Double、Integer、Long类型的元素。
    InfiniteSupplyingSpliterator的数据源为生成函数,只有一个特征值IMMUTABLE。它的trySplit()方法是在子类中实现的。下面以OfRef为例做介绍:

                public Spliterator<T> trySplit() {
                	//estimate表示元素个数,一般情况下将其设置为Long.MAX_VALUE
                    if (estimate == 0)
                        return null;
                    //s表示生成函数
                    return new InfiniteSupplyingSpliterator.OfRef<>(estimate >>>= 1, s);
                }
    

    trySplit()方法没有调用生成函数获取元素的动作,只是设置元素个数为当前剩余元素的一半,然后新建一个OfRef对象。

    cs