jdk8 Stream 解析2 - Spliterator分割迭代器

概述

说完Stream 的一些源码解析。我们看下我们的 Stream 是如何产生的。我们最为常见的流的产生方式是 collection.stream(), 你点开Stream()方法, 他是通过 Collection 这个上层接口两个java8 新增特性 default method 进行实现。

image_1bf3u1l911o5rf825gv39aqe9.png-41.3kB

这就牵扯到一个关键要点 Spliterator :分割迭代器。

本文主要描述 Spliterator 的作用,大部分来源于你不愿意读的 JavaDoc

Spliteratorjdk8 非常重要的概念。里面的细节很值得学习玩味。

Spliterator 冗长的JavaDoc 说了什么?

基本介绍

Spliterator 是一个对源(数组,集合,io流等等)中元素进行遍历和分区的类。

可以通过 tryAdvance() 方法逐个遍历,也可以按照 forEachRemaining() 方法进行按 bulk 逐块的遍历。(内部调用的还是tryAdvance

Spliterator 有类似 Collector 中的 characteristics , 但都是由十六进制来表示的。

  • SIZED :表示大小固定, Collection常用
  • DISTINCT : 去重, Set常用
  • SORTED : 有顺序的 SortedSet 会用
    等等

原生类型的特化版本

特化分割迭代器也被提供,和Stream类似。减少装箱和拆箱的操作
image_1bf3vbfdl10r5vl7ef8t47kium.png-128.9kB

比迭代器Iterator 更加高效的遍历元素的方式

提供更加高效的方法,进行数据的迭代。
Iterator 的使用需要调用两个组合方法 hasNext() 以及 next() ,同事在多线程访问的情况下还会出现竞争,你需要去同步。
而分割迭代器 Spliterator 使用函数式编程的方式,只用一个方法就可以做到这个两个函数动作。就避免了竞争 ,就是 tryAdvance() 方法。后面会介绍

Spliterator的接口方法

tryAdvance()

同时做了 hasNext() 以及 next() 的工作。

1
2
3
4
/**
* 对给定的元素进行判断,如果满足条件就会执行 Action
*/
boolean tryAdvance(Consumer<? super T> action);

forEachRemaining()

是一个默认方法,对余下的元素进行操作,直到元素全部被遍历完
一般情况下回直接调用上面的tryAdvance() 方法,但是也可以更具需要进行重写。

1
2
3
4
5
6
7
/**
* 对余下的元素进行操作,直到元素全部被遍历完
* 如果源是有序的,遍历也是有序的
*/
default void forEachRemaining(Consumer<? super T> action) {
do { } while (tryAdvance(action));
}

这里有一点很值得注意,方法体中的 do {} 是空的,这个是因为 tryAdvance() 方法本身就完成了两个操作 hasNext() 以及 next(),所以方法体中不需要有任何操作了。这个是 函数式编程带来的好处。以及与命令式编程的区别。

trySplit()

尝试切分源来的 Spliterator, 返回的是(注意!!!)返回的是 分割出来的那一部分 数据,原有的数据集将不在包含这部分数据集合。两者 没有交集。剩下的可以继续分割,也许不可以继续分割了

举个例子,我原来有 100个元素,我通过 trySplit 切分出 30 个,作为一个新的 分割迭代器 返回,原有的,就还剩下 70 个。

  • 如果是原有数据集合是 ORDERD 的,分出来的也是有序的。
  • 除非元素数量是无穷的,否则,最后一定会出现不能在分割的情况,这种情况下,返回的结果是 null
1
Spliterator<T> trySplit();

estimateSize()

估算集合剩余给forEachRemaining大小,不一定精确。
但是如果这个 SpliteratorSIZED,没有被遍历或者 split, 或是 SUBSIZED的,没有被遍历,那么他这个值一定是准确的。

1
long estimateSize();

还有个与之相关的默认方法,就是利用这个特性。

1
2
3
default long getExactSizeIfKnown() {
return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
}

characteristics()

表示集合的特性,一共8个。

  • 分割之前,返回的结果都是一致的

如果返回结果不一致,则操作是不受保证的

  • 而分割之后,不保证一致

有一个默认方法用于判断 Spliterator 是否包含这个特性

1
2
3
default boolean hasCharacteristics(int characteristics) {
return (characteristics() & characteristics) == characteristics;
}

getComparator

如果源是SORTED 类型的,且有比较器 Comparator 的话,则返回这个 Comparator,如果是SORTED 类型的,但是没有比较器,则返回 null , 除此之外,都抛出异常

接口的默认方法里,就是抛出了异常

1
2
3
default Comparator<? super T> getComparator() {
throw new IllegalStateException();
}

Spliterator的8个Characteristics 特性

ORDERED

源的元素有序,tryAdvanceforEachRemainingtrySplit 都会保证有序的进行元素的处理

  • 需要注意 hashSet 这类 Collection 是不保证有序的
  • ORDERED 特性的数据,在并发计算的时候客户端也要做顺序限制的保证

DISTINCT

太简单,唯一性。 类似 Set 这样的传入集合会拥有这样的特性

SORTED

有这种特性的 Spliterator ,有一个特定的顺序。或者是所有元素都是可比较的,或者是有特定的比较器。

  • SORTED 一定会有 ORDERED

SIZED

有这种属性的 Spliterator 在遍历和分割之前,estimateSize() 返回的大小是固定的,并且是准确的。

NONNULL

不为 NULL, 大部分并发的集合,队列,Map 都可能会有这样的特性。

IMMUTABLE

不可变的。元素遍历期间不可以被 添加,替换,删除(cannot be added, replaced, or removed)
否则,应该抛出异常。

CONCURRENT

支持并发操作的。

  • 顶层的 Spliterator 不可以 CONCURRENTSIZED。 这两者是相互冲突的。
  • 但是分割之后的 Spliterator , 可能是 SIZED, 顶层不能决定底层

SUBSIZED

trySplit()被分割后的所有分割迭代器都是 SIZED 以及 SUBSIZED 的。
如果分割后,没有按照要求返回SIZED 以及 SUBSIZED 属性,那么操作是不被保证的,也就是结果不可预测。

这个属性和 SIZED 的区别就是, SIZED 不保证 SUBSIZED。而 SUBSIZED 会要求保证 SIZED

内部特化而做的函数式接口 (OfPrimitive)

除了上面的函数,以及特性,Spliterator 迭代器中,还有几个定义在内部的接口。

image_1bf4821k2kft1nsq13981l5j1h1d13.png-207.1kB

OfPrimitive 重载了(overloads)了 Spliterator 的方法。用于实现特化的分割迭代器。

image_1bf487rnqvk11cpuoeve911ss81g.png-41.2kB

overloads:返回类型名称一致,参数不一致。
注意与 override 的区别

一个颠覆面向对象编程常识的现象

请大家看图
image_1bf48rn8nbi1lrs1hgq1f72ass1t.png-236.6kB

这两次类型转换奇怪的地方是:

IntConsumerConsumer 两个接口,没有继承关系,两个接口是平行的。 这样的转换,在之前是不可能成功的。 简直是颠覆认知啊!

我悄悄说啊(并没有。。你也基本不会关心不是么。。)

但是在函数式编程中能转换成功呢? 你可以等我下一篇文章~

朱老师&敏哥 wechat
有惊喜,朋友🙄
我要拿铁不加糖.