Java分割迭代器看这篇就够了!

140 阅读4分钟

Java分割迭代器

Spliterator的名字来源于"Split" 和 "Iterator"分割迭代器。

Spliterator 和Iterators一样,用于遍历源元素。如果你不知道Iterators是什么,请看另一篇介绍Iterators的文章。除此之外,它的设计还支持将数据分块,供并行处理使用(几乎所有Iterator不支持并行访问迭代),大大提高访问效率。

Spliterator实现

以Vector的分割迭代器为例,我们来看看它的实现。VectorSpliterator中维护了四个变量,分别是:

Object[] array,源数据的完整引用。array实现了懒加载的方式,只有在第一次调用VectorSpliterator方法时才会将源数据的引用传递至array。为什么要这样做?尽量晚的让array获得引用可以让当前线程在需要的时候立即看到源数据发生的变化。如果直接使用源数据的引用或者在构造时就传入源数据的引用,那么其他线程对源数据的修改无法反映到当前线程。

int fence,索引上限,任何时候必须满足index<=fence。

int index,当前索引,也就是该分割迭代器指向的下标。index只能递增,结合fence索引上限,就可以直接圈定该分割迭代器的数据访问范围。

int expectedModCount,期待模值。作用和Iterator中的完全一样,用于确保元素未发生结构性变化。但有一个重要区别:Iterator总是先执行操作再检查expectedModCount,而Iterator总是先检查,检查通过后才会执行操作。

使用Spliterator遍历

Iterator中有方法next用于返回下一个元素。Spliterator无法返回然和元素,而是直接接收一个消费者参数,然后对元素执行对应操作。这个方法就是boolean tryAdvance(Consumer<? super E> action),通常所有实现都会在执行后使索引增加,最后检查模值。

同样的,Spliterator也有对剩余所有元素执行操作的forEachRemaining方法。

Spliterator的多线程遍历

尽管分割器在并行算法中的作用显而易见,但我们并不指望它是线程安全的;相反,使用分割器的并行算法的实现应确保分割器每次只被一个线程使用。这个特点和Iterator一致。

一般来说,这很容易通过线程封闭来实现,也就是一个线程持有一个分割迭代器实例。这通常是通过递归分解工作的典型并行算法的自然结果。调用trySplit() 的线程可以将返回的 Spliterator 交给另一个线程,后者可以遍历或进一步拆分该 Spliterator。如果两个或更多线程同时对同一分割器进行操作,分割和遍历的行为将无法定义。如果原始线程将拆分器移交给另一个线程处理,最好在使用tryAdvance() 消耗任何元素之前进行移交,因为只有在遍历开始之前,某些保证(例如SIZED拆分器的estimateSize()的准确性)才会有效。

通常使用Spliterator<E> trySplit()方法进行拆分,设index到fence的中点为mid,那么通常的集合实现中会将mid-fence的部分保留,返回index-mid的部分作为一个新的Spliterator。

Spliterator特征值

Spliterator通常不直接使用外部数据源,而是在内部持有一个外部数据源的引用array。而且这还是Object数组,丢失了类型信息。Spliterator不知道外部数据的类型是List,还是Set什么的。为了描述外部数据源的特征,Spliterator引入了characteristics属性。

characteristics表示这个Spliterator的特征,Spliterator有8大特征:

public static final int ORDERED    = 0x00000010;//表示元素是有序的(如List)
public static final int DISTINCT   = 0x00000001;//表示元素不重复(如Set)
public static final int SORTED     = 0x00000004;//表示元素是按一定规律进行排列(有指定比较器,对于 SORTED 的元素,它一定是 ORDERED 的)
public static final int SIZED      = 0x00000040;//表示大小是固定的
public static final int NONNULL    = 0x00000100;//表示没有null元素
public static final int IMMUTABLE  = 0x00000400;//表示元素不可变
public static final int CONCURRENT = 0x00001000;//表示数据源可以多线程操作(如ConcurrentHashMap)
public static final int SUBSIZED   = 0x00004000;//表示子Spliterators都具有SIZED特性

一个Spliterator可以有多个特征,多个特征进行or运算,最后得到最终的characteristics。

除此之外,Spliterator还是实现Stream的重要工具,我会在Stream篇中重点介绍它们的关系。