目录
Spliterator简介
tryAdvance
forEachRemaining
trySplit
estimateSize
getExactSizeIfKnown
characteristics,hasCharacteristics,getComparator
各种特征
OfPrimitive
OfInt
public interface Spliterator {
Spliterator是一个可分割迭代器(splitable iterator),可以和iterator顺序遍历迭代器一起看。jdk1.8发布后,对于并行处理的能力大大增强,Spliterator就是为了并行遍历元素而设计的一个迭代器,jdk1.8中的集合框架中的数据结构都默认实现了spliterator
下面是我对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 super T> action) {
* for (; origin < fence; origin += 2)
* //从origin到fence,每次+2,使用action.accept,参数为对应的元素
* action.accept((T) array[origin]);
* }
*
* public boolean tryAdvance(Consumer super T> 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
就是用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 super T> action);
有默认的实现,重复调用tryAdvance
/**
*
* 在当前线程,以连续的顺序,对每个剩余的元素执行给定的操作,直到所有元素已经被处理或者操作抛出异常。
* 如果这个spliterator是ordered的,操作以遇到的顺序执行。异常抛出给调用者。
*
默认实现重复调用tryAdvance直到它返回false。如果有需要时应该被覆盖。
*
*
* @param action The action
* @throws NullPointerException if the specified action is null
*/
default void forEachRemaining(Consumer super T> action) {
do { } while (tryAdvance(action));
}
如果这个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();
返回一个根据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();
/**
*
* 如果spliterator是sized的,就返回estimateSize的结果(这个是准确的数量),否则返回-1。
* 默认的方法就是这样干的。
*
* @return the exact size, if known, else {@code -1}.
*/
default long getExactSizeIfKnown() {
return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
}
/**
* 返回这个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 super T> 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;
这三个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
下面的这三个方法就是如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 super Integer> 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 super Integer> 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);
}
}
}