java 接口Spliterator源码分析

目录

Spliterator简介

tryAdvance

forEachRemaining

trySplit

estimateSize

getExactSizeIfKnown

characteristics,hasCharacteristics,getComparator

各种特征

OfPrimitive

OfInt


Spliterator简介

public interface Spliterator {

Spliterator是一个可分割迭代器(splitable iterator),可以和iterator顺序遍历迭代器一起看。jdk1.8发布后,对于并行处理的能力大大增强,Spliterator就是为了并行遍历元素而设计的一个迭代器,jdk1.8中的集合框架中的数据结构都默认实现了spliterator

java 接口Spliterator源码分析_第1张图片 

下面是我对jdk注释的对应翻译,看完基本上就大概懂了一大半了

可以看到注释中,还有jdk自带的一个实现spliterator接口的案例,和使用它的样例,值得参考

/**
 * 
 * 

一个类,为了遍历并且分割一个来源的元素。这些元素可以被一个spliterator覆盖,举个例子, * 一个数组,一个collection,一个io的channel或者一个工厂功能 * *

一个spliterator可能独自地遍历元素(通过tryAdvance)或者成批地,连续地遍历(通过forEachRemaining) * *

一个spliterator可能隔开它的一些元素(使用trySplit)作为另一个spliterator,用作可能并发的操作。 * 使用一个不能分割或者以高度不平衡或者低效率的方式分割的spliterator的行为,很可能不会因为并发收益。 * 遍历和分割耗尽元素,每个spliterator队一个单独的块计算有效 * *

一个spliterator也能报告一个它的结构,来源,元素的特征集(通过characteristics()),包括 * ordered,distinct,sorted,sized,nonnull,immutable,concurrent,subsized。 * 这些可能会被spliterator的使用者使用,来控制,特殊化或者简单化计算。 * 例如,一个collection的spliterator会报告sized,set报告distinct,sorted set报告distinct和sorted * 特征集会被以一个简单的bitset的形式被报告。 * *

一些特征集额外地限制方法的行为。例如,如果是ordered,遍历方法必须符合他们表明的有序。 * 续保的特征集可能会在未来被定义,所以实现类不应该对未被标明的值指派意义。 * *

一个没有报告immutable或者concurrent的spliterator应该有一个文档化的策略: * 当spliterator与元素来源绑定,并且对元素来源的结构的干扰会在绑定后被检测到。 * 一个迟绑定的spliterator在第一次遍历,第一次分割或者第一次对预计大小查询时与元素来源绑定,而不是在spliterator被创建时绑定。 * 一个不是迟绑定的spliterator在创建或者任何方法的第一次调用时绑定。 * 在绑定之前对来源的修改会在spliterator遍历时显示出来。 * 在绑定一个spliterator后,应该基于一个最大努力的假设,如果结构的干扰被检测出,则抛出一个ConcurrentModificationException的错误。 * 如此做的spliterator被称为fail-fast,快速失败。 * spliterator的成块遍历的方法(forEachRemaining)可能优化遍历并且在所有元素被遍历完后检查对结构的干扰,而不是遍历每个元素时都检查,然后立即失败。 * *

spliterators 能提供一个预计残留的元素的数目,通过estimatesize方法。 * 理想上,当体现在特征为sized时,这个值准确地对应元素的多少,那会导致一个完美的遍历。 * 然而,甚至当没有准确地知道时,一个预估大小的值可能对源头的操作有用,例如帮助决定是继续分割还是连续地遍历剩余的元素哪个更有利。 * *

尽管spliterators明显在并发算法中很实用,但是它们不有被预计为是线程安全的。 * 相反,使用spliterators的并法算法的实现,应该确保spliterator应该同一时间只被一个线程使用。 * 这通常很容易达到,通过串行线程封闭,这通常是一个通过递归的分解来工作的典型并发算法的自然结果。 * 一个调用trysplit的线程可能移交返回的spliterator给另一个线程,它可能轮流遍历这个spliterator或者分割它。 * 如果两个或以上的线程并发地操作同一个spliterator时,分割或者遍历的行为是不确定的。 * 如果最初的线程将一个spliterator转交给另一个线程来处理,那么最好在任何元素被tryAdvance消费前传递它, * 因为某些保证(例如sized的spliterator的estimatesize方法的准确性)仅仅在遍历开始前有效。 * *

spliterator对基本类(int,long,double)的实现被提供了。 * 这些子类的默认实现的tryAdvance和forEachRemaining包装了基本值对他们对于的包装类。 * 这样的包装可能破坏任何通过使用基本类型获得优势的行为。 * 为了避免包装,对于的基于基本类型的方法应该被使用。 * 例如OfInt的tryAdvance(IntConsumer)和forEachRemaining(IntConsumer)方法应该被使用, * 优先于tryAdvance(Consumer)和forEachRemaining(Consumer) * 使用基于包装类的方法(tryAdvance和forEachRemaining)遍历基本值,不影响值转换为包装值的顺序。 * *

spliterators,像iterator,是为了遍历一个来源的元素。 * spliterator这个api被设计用来提供有效的并发遍历,取代了连续遍历,通过支持分解和单元素迭代。 * 此外,通过一个spliterator访问元素的协议,被设计用来让每个元素的开销比iterator小, * 并且避免有hasNext和next这样的继承的方法 * *

对于可更改的来源,随意的和非确定性的行为可能发生,如果来源的结构在spliterator与来源绑定和遍历结束之间,被元素添加,替换,删除等行为干扰。 * 举个例子,当使用stream框架是,这些行为会产生随意的随意的和非确定性的结果。 * *

Structural interference of a source can be managed in the following ways * (in approximate order of decreasing desirability): * *

对来源的结构的干扰能以下面的方法管理(通过降低吸引力的近似顺序) *

1 来源的结构不能被干扰。比如CopyOnWriteArrayList是个不可更改的来源。 * 从这种来源产生的spliterator会报告特征immutable *

2 来源管理并发的修改,比如ConcurrentHashMap是个并发的来源,这种会报告concurrent *

3 可变的来源提供一个迟绑定的和fail-fast的spliterator。迟绑定缩短了干扰影响计算的窗口期。 * fail-fast机制以基于最大努力的假设,探测出在遍历开始后的干扰并抛出ConcurrentModificationException * 比如,arraylist和许多其他的jdk中的肺病发的类,提供一个迟绑定,fail-fast的spliterator *

4 可变的来源提供一个非迟绑定,fail-fast的spliterator。 * 来源增长了抛出ConcurrentModificationException的可能性,因为增加了干扰影响计算的窗口期。 *

5 可变的来源提供一个迟绑定,非fail-fast的spliterator。 * 这个来源受到遍历开始后进行随意的,非确定性操作的影响,因为干扰没有被检测到。 *

6 可变的来源提供一个非迟绑定,非fail-fast的spliterator。 * 这个来源增加了随意的,非确定性操作的风险,因为没有被探测到的干扰可能在建造后发生。 * * *

举个例子,下面是一个类(不是很有用,作为例子),它维持着一个数组,里面在偶数位置有真正的数据, * 奇数位置有不相关的标签数据。它的spliterator忽视这些标签。 * *

 {@code
 * class TaggedArray {
 *   private final Object[] elements; // 数组不可更改
 *   TaggedArray(T[] data, Object[] tags) {
 *     int size = data.length;
 *     if (tags.length != size) throw new IllegalArgumentException();
 *     this.elements = new Object[2 * size];
 *     for (int i = 0, j = 0; i < size; ++i) {
 *       elements[j++] = data[i];
 *       elements[j++] = tags[i];//交替放入数据
 *     }
 *   }
 *
 *   public Spliterator spliterator() {
 *     return new TaggedArraySpliterator<>(elements, 0, elements.length);
 *   }
 *
 *   static class TaggedArraySpliterator implements Spliterator {
 *     private final Object[] array; //这个array就是TaggedArray的elements
 *     private int origin; // 现在的index,用于分割或者遍历
 *     private final int fence; // 栅栏,最大的index(不能到达)
 *
 *     TaggedArraySpliterator(Object[] array, int origin, int fence) {
 *       this.array = array; this.origin = origin; this.fence = fence;
 *       //初始化时是 elements, 0, elements.length
 *     }
 *
 *     public void forEachRemaining(Consumer action) {
 *       for (; origin < fence; origin += 2)
 *       //从origin到fence,每次+2,使用action.accept,参数为对应的元素
 *         action.accept((T) array[origin]);
 *     }
 *
 *     public boolean tryAdvance(Consumer action) {
 *       if (origin < fence) {
 *       //如果origin trySplit() {
 *       int lo = origin; // divide range in half
 *       int mid = ((lo + fence) >>> 1) & ~1; // 让mid为偶数(与11110与操作,前面不变,最后边0,变成偶数)
 *       if (lo < mid) { // 将左半部分分割给调用者,自己处理右半部分
 *         origin = mid; // reset this Spliterator's origin
 *         return new TaggedArraySpliterator<>(array, lo, mid);
 *       }
 *       else       // too small to split
 *         return null;
 *     }
 *
 *     public long estimateSize() {
 *       return (long)((fence - origin) / 2);  //预估剩余处理元素
 *     }
 *
 *     public int characteristics() {
 *       return ORDERED | SIZED | IMMUTABLE | SUBSIZED; //几个元素进行或运算,最后应该是可分割的
 *     }
 *   }
 * }}
* *

下面是一个并发计算框架,例如stream包,将使用spliterator来并发计算的例子, * 下面是一个实现并发关联运算的例子,阐述了基本的使用习惯:分割子任务,直到预估的工作足够小,以至于能够连续处理。 * 现在我们假设处理子任务的顺序不重要,不同的任务可能更多地并发分割并且处理元素,以不能确定的顺序。 * 这个例子使用了CountedCompleter,类似的使用方式也应用于其他的并发任务结构。 * *

{@code
 * static  void parEach(TaggedArray a, Consumer action) {
 *   Spliterator s = a.spliterator(); //先用TaggedArray创建一个spliterator
 *   long targetBatchSize = s.estimateSize() / (ForkJoinPool.getCommonPoolParallelism() * 8); //预估spliterator的大小/一个数字
 *   new ParEach(null, s, action, targetBatchSize).invoke();
 * }
 *
 * static class ParEach extends CountedCompleter {
 *   final Spliterator spliterator;
 *   final Consumer action;  
 *   final long targetBatchSize;  //每次处理元素的多少
 *
 *   ParEach(ParEach parent, Spliterator spliterator,
 *           Consumer action, long targetBatchSize) {
 *     super(parent);
 *     this.spliterator = spliterator; this.action = action;
 *     this.targetBatchSize = targetBatchSize;
 *   }
 *
 *   public void compute() {
 *     Spliterator sub;
 *     while (spliterator.estimateSize() > targetBatchSize &&
 *            (sub = spliterator.trySplit()) != null) {
 *            //如果spliterator的estimateSize>targetBatchSize并且spliterator通过trySplit返回的sub不为空
 *       addToPendingCount(1); 调用addToPendingCount
 *       new ParEach<>(this, sub, action, targetBatchSize).fork(); 
 *       //创建新的运行任务ParEach,将sub放进去(第一次是一半,第二次是1/4,直到剩余元素小于targetBatchSize)
 *     }
 *     spliterator.forEachRemaining(action); //spliterator处理剩余的元素,此时剩余元素小于targetBatchSize
 *     propagateCompletion(); //完成任务
 *   }
 * }}
* *

如果tripwire的系统boolean变量被设置成true,如果当操作基本子类型时,会发生基本类型的包装,然后会报告诊断的错误 * * @param the type of elements returned by this Spliterator * * @see Collection * @since 1.8 */ public interface Spliterator

tryAdvance

就是用action执行下个元素,实现的重点在于下个元素如何出现,以及数据结构中index的变化

/**
     * 

如果剩余一个元素,使用给定的action执行它,返回true,否则返回false *

如果这个spliterator是ordered的,这个action按照相遇的顺序,对下一个元素进行操作 *

异常抛出给调用者 * * @param action The action * @return {@code false} if no remaining elements existed * upon entry to this method, else {@code true}. * @throws NullPointerException if the specified action is null */ boolean tryAdvance(Consumer action);

forEachRemaining

有默认的实现,重复调用tryAdvance

    /**
     * 
     * 

在当前线程,以连续的顺序,对每个剩余的元素执行给定的操作,直到所有元素已经被处理或者操作抛出异常。 * 如果这个spliterator是ordered的,操作以遇到的顺序执行。异常抛出给调用者。 *

默认实现重复调用tryAdvance直到它返回false。如果有需要时应该被覆盖。 * * * @param action The action * @throws NullPointerException if the specified action is null */ default void forEachRemaining(Consumer action) { do { } while (tryAdvance(action)); }

trySplit

如果这个spliterator能够被分割,返回一个spliterator,它能包含一部分元素,能这部分就不会被这个spliterator包含。

就是比如这个对应1-8,返回的spliterator对应1-4,这个现在只能对应5-8了

    /**
     * 

如果这个spliterator能够被分割,返回一个spliterator, * 它能包含一部分元素,能这部分就不会被这个spliterator包含。 * *

如果这个spliterator是ordered的,返回的spliterator必须包含有严格前缀的元素 * *

除非这个spliterator包含无限的元素,重复地调用trySplit一定会最后返回null,之前返回非null * *

在分割前estimateSize方法返回的值必须大于等于分割后estimateSize方法返回的值。 * *

如果这个spliterator是subsized的,在分割前estimateSize方法返回的值必须等于分割后estimateSize方法返回的值 * *

这个方法可能因为一些原因返回null,包括元素为空,遍历开始后不能分割,数据结构的限制和出于效率的考虑 * *

一个理想的trySplit方法有效地(不包括遍历)恰好分割了它一半的元素,从而允许平衡的并发计算。 * 许多背离了这个理想保持着高度的有效。 * 比如,仅仅大约地分割一个大约平衡的树或者一个叶子节点仅包含1个或2个元素的树不能继续分割这些节点。 * 然而,巨大的偏离平衡或者非常无效的trySplit结构会典型地导致非常差的并发效果 * * * @return a {@code Spliterator} covering some portion of the * elements, or {@code null} if this spliterator cannot be split */ Spliterator trySplit();

estimateSize

返回一个根据forEachRemaining遍历会遇到的元素的个数或者如果元素是无限的,未知的,计算太昂贵的,返回long的max_value

    /**
     * 
     * 

返回一个根据forEachRemaining遍历会遇到的元素的个数或者如果元素是无限的,未知的,计算太昂贵的,返回long的max_value * *

如果这个spliterator是sized的,并且没有被部分遍历过或者分割,或者这个spliterator是subsized并且没有被部分遍历过, * 这个估计必须是个准确的通过一次完整的遍历会遇到的元素的个数。 * 然而,这个估计可能是不准确的,但经过调用trySplit,一定会降低。 * *

甚至一个不准确的估计也是有用的并且计算起来不昂贵。 * 比如,一个大概平衡的二叉树的子spliterator可能返回一个值,这个值是它的父spliterator返回的一半。 * 如果根spliterator没有保存准确的数目,它能估计的大小是2的它的最大深度次方。 * * * @return the estimated size, or {@code Long.MAX_VALUE} if infinite, * unknown, or too expensive to compute. */ long estimateSize();

getExactSizeIfKnown

    /**
     * 
     * 

如果spliterator是sized的,就返回estimateSize的结果(这个是准确的数量),否则返回-1。 * 默认的方法就是这样干的。 * * @return the exact size, if known, else {@code -1}. */ default long getExactSizeIfKnown() { return (characteristics() & SIZED) == 0 ? -1L : estimateSize(); }

characteristics,hasCharacteristics,getComparator

    /** 
     * 

返回这个spliterator的元素的特征的集合。 * 结果被表达成ORDERED,DISTINCT,SORTED,SIZED,NONNULL,IMMUTABLE,CONCURRENT,SUBSIZED的或值。 * 在trySplit前或者两个trySplit之前重复调用characteristics应该总是返回相同的结果。 * *

如果一个spliterator报告一个不一致的特征集合(不管返回给一个调用或者多个调用),使用这个spliterator不能保证任何计算 * *

一个spliterator的特征在分割前和分割后可能不同。典型的例子是sized,subsized,concurrent * * @return a representation of characteristics */ int characteristics(); /** * *

如果这个spliterator包含了给定的所有特征,返回true * 默认的实现,如果给定的特征的位被设定了,就返回true * * @param characteristics the characteristics to check for * @return {@code true} if all the specified characteristics are present, * else {@code false} */ default boolean hasCharacteristics(int characteristics) { return (characteristics() & characteristics) == characteristics; //自己的特征&给的特征,如果自己的在给的之中,则比如会返回自己的特征 } /** *

如果spliterator的来源是sorted,通过一个comparator,则返回这个comparator。 * 如果来源是sorted,以自然地顺序,返回null。 * 然而,如果来源不是sorted,抛出IllegalStateException。 * *

默认实现总是抛出IllegalStateException。 * * * @return a Comparator, or {@code null} if the elements are sorted in the * natural order. * @throws IllegalStateException if the spliterator does not report * a characteristic of {@code SORTED}. */ default Comparator getComparator() { throw new IllegalStateException(); }

各种特征

    /**
     * 
     * 

特征值,代表着为元素定义了一个相遇的顺序。如果这样,spliterator保证trySplit严格地按照元素的前缀分割。 * tryAdvance以前缀的顺序,一次前进一个元素。forEachRemaining以相遇的顺序执行操作。 * *

一个Collection有一个相遇的顺序,如果对应的iterator报告有顺序。 * 如果这样,相遇的顺序和报告的顺序相同。否则,一个collection确实没有一个相遇的顺序。 * *

对于任何list,相遇舜玉被保证是上升的index顺序。但是对于基于哈希的collection,例如hashset,不能保证顺序。 * 一个ordered的spliterator的用户,希望在非交替的并发的计算中,保持顺序的限制 * */ public static final int ORDERED = 0x00000010; /** * *

特征值,代表着spliterator的所有元素,两两使用x.equals(y) 都为false,意味着它们没有一个相同。 * 这个请求,举个例子,对于一个基于set的spliterator。 */ public static final int DISTINCT = 0x00000001; /** * *

特征值,代表着相遇顺序遵循着一个定义过的排序顺序。如果这样,getComparator返回一个关联的comparator, * 或者如果所有元素是comparable并且以自然的顺序排序,返回null。 * *

一个sorted的spliterator必须也是ordered的。 * *

jdk中的实现navigableset或者sortedset的collecion,的spliterator,是sorted的 */ public static final int SORTED = 0x00000004; /** * *

特征值,代表着在遍历或者分割前返回的estimateSize代表了一个有限的大小, * 无视了数据结构来源的变动,代表了一次完整遍历会遇到的准确数字。 * *

collection的大部分spliterator,如果有这个特征,包含了它所有的元素。 * 子spliterator,例如hashset,包含了元素的一个子集并且近似于它们报告的大小。 */ public static final int SIZED = 0x00000040; /** *

特征值,代表着这个来源保证相遇的所有元素不会为null。 * 例如,这适用于大部分collections,queues,map */ public static final int NONNULL = 0x00000100; /** *

特征值,代表着元素来源的结构不能被改变。元素不能被添加,代替,删除,在遍历过程中,这些改变不能发生。 * 一个不是immutable或者concurrent被希望有一个文档化的政策(例如抛出ConcurrentModificationException) * 当在遍历中涉及到探测出结构的干扰。 */ public static final int IMMUTABLE = 0x00000400; /** * *

特征值,代表着元素的来能可能被安全地,并发地修改,(允许增加,代替,和/或者删除)通过没有多余同步的多线程。 * 如果这样,spliterator被期待有一个文档化的策略,关于在遍历中修改的影响。 * *

一个最高层的spliterator应该不同时是concurrent和sized的, * 因为有限的大小(如果已知),可能在遍历中并发地修改时,大小变化。 * 这样的spliterator是不一致的,并且不能保证任何使用这个spliterator的计算。 * 子spliterator可能是sized,如果分割后的大小是已知的,并且当遍历时,对来源的增加或删除不会反应到遍历中。 * *

大部分并发的集合有一个一致性策略,保证在spliterator创建时元素的准确性,但可能不会反应出后来的增加或者删除。 */ public static final int CONCURRENT = 0x00001000; /** *

特征值,代表着所有的通过trySplit产生的spliterator都是sized和subsized。 * 这意味着所有的子spliterator,无论是亲的或者不亲的,都是sized的。 * *

如果一个spliterator没有按照subsized,报告sized, * 那它是不一致的,并且不能保证使用这个spliterator做出的任何计算。 * *

一些spliterator,例如对一颗接近平衡的二叉树的顶级spliterator,是sized,但不是subsized, * 因为很容易知晓整棵树的大小,但不知道子树的具体大小。 */ public static final int SUBSIZED = 0x00004000;

OfPrimitive

java 接口Spliterator源码分析_第2张图片

这三个T型参数很重要,比spliterator多加了这三个方法

    /**
     * 一个专门为基本类型的spliterator
     *
     * @param  被这个spliterator返回的元素的类型。这个类型必须是一个基本类型的包装类,例如Integer对于基本类型int。
     * 
     * @param  原始的conumser的类型。这个类型必须是对T的consumer类的基本实例。例如IntConsumer对于Integer、
     * 
     * @param  基本spliterator的类型。这个类型必须是对T的spliterator的基本实例,例如Spliterator.OfInt对于Integer
     * 
     * 

这个类里重写了trySplit,返回T_SPLITR类型的spliterator。 * *

还增加了两个方法tryAdvance,forEachRemaining,里面的参数为T_CONS * * @see Spliterator.OfInt * @see Spliterator.OfLong * @see Spliterator.OfDouble * @since 1.8 */ public interface OfPrimitive> extends Spliterator

OfInt

java 接口Spliterator源码分析_第3张图片

下面的这三个方法就是如OfPrimitive覆盖的那三个方法。 

//下面的这三个方法就是如OfPrimitive覆盖的那三个方法。

        @Override
        OfInt trySplit();

        @Override
        boolean tryAdvance(IntConsumer action);

        @Override
        default void forEachRemaining(IntConsumer action) {
            do { } while (tryAdvance(action));
        }
        /** 
         * 如果这个action是IntConsumer的实例,将它转为IntConsumer并且转为调用tryAdvance(IntConsumer)方法。
         * 否则这个action会被适配为IntConsumer的实例,通过包装IntConsumer的参数,然后传递给IntConsumer。
         */
        @Override
        default boolean tryAdvance(Consumer action) {
            if (action instanceof IntConsumer) {
                return tryAdvance((IntConsumer) action);
            }
            else {
                if (Tripwire.ENABLED)
                    Tripwire.trip(getClass(),
                                  "{0} calling Spliterator.OfInt.tryAdvance((IntConsumer) action::accept)");
                return tryAdvance((IntConsumer) action::accept);
            }
        }

        /**
         * {@inheritDoc}
         * @implSpec
         * If the action is an instance of {@code IntConsumer} then it is cast
         * to {@code IntConsumer} and passed to
         * {@link #forEachRemaining(java.util.function.IntConsumer)}; otherwise
         * the action is adapted to an instance of {@code IntConsumer}, by
         * boxing the argument of {@code IntConsumer}, and then passed to
         * {@link #forEachRemaining(java.util.function.IntConsumer)}.
         */
        @Override
        default void forEachRemaining(Consumer action) {
            if (action instanceof IntConsumer) {
                forEachRemaining((IntConsumer) action);
            }
            else {
                if (Tripwire.ENABLED)
                    Tripwire.trip(getClass(),
                                  "{0} calling Spliterator.OfInt.forEachRemaining((IntConsumer) action::accept)");
                forEachRemaining((IntConsumer) action::accept);
            }
        }
    }

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(源码分析,java容器)