Spliterator<T> 接口

文档说明

  • 一个用于对一个源当中的元素进行遍历和分区的对象
  • 一个 Spliterator 涵盖的源中的元素可以是数组、Collection、IO通道、生成器函数
  • 一个 Spliterator 可以一个一个地遍历元素(tryAdvance()),也可以顺序地分块遍历(forEachRemaining())
  • 一个 Spliterator 可以对其元素使用 trySplit 进行分区形成另外的 Spliterator,使用在并行操作中
  • 操作中使用的 Spliterator 但无法进行分割,或者分割结果高度不平衡或低效,则操作不能从并行当中获益
  • 遍历以及分割都会消耗掉元素,每一个 Spliterator 只对其对应的单个块进行运算
  • 一个 Spliterator 还会去报告一个装有其构造、源以及其元素的特性值的集合,特性值有:ORDERED、DISTINCT、SORTED、SIZED、NONNULL、IMMUTABLE、CONCURRENT、SUBSIZED
  • 这些特性可被 Spliterator 的使用者调用,以特化或简化计算
  • 特性值都是通过位操作进行标识的
  • 部分特性值会额外地限定方法的行为
    • 如:ORDER,遍历方法必须遵循它们在文档中定义好的顺序
  • 未来可能还会定义新的特性值,因此实现者不应该给8个特性值以外的词赋予新的含义
  • 当一个 Spliterator 不包含 `IMMUTABLE` 或 `CONCURRENT` 特性时,期望能作出如下的文档化策略考量
    • 当 Spliterator 绑定到元素的源上时,需要对元素进行结构上的检测
  • 快速失败
    • 一个延迟绑定的 Spliterator 是在首次遍历,或首次分割,或首次查询大小时绑定到源上,而非在其创建时绑定
    • 一个非延迟绑定的 Spliterator 在构造或任意一个方法首次调用时即被绑定
    • 在绑定之前,若对源进行了修改,则该修改会在 Spliterator 遍历时反映出来
    • 当绑定到 Spliterator 后,继续对源进行修改,则会抛出 ConcurrentModificationException
    • Spliterator 上述的的定义称为 “快速失败”(fail-fast)
  • Spliterator 的块遍历方法 forEachRemaining() 会优化遍历,并且在所有元素遍历完成后检查结构上的变化,而非在遍历元素时逐个检查
  • Spliterator 可以通过 estimateSize() 方法获取待遍历元素数量的估算值
    • 理想情况下,若特性值中包含了 `SIZED`,则 estimateSize 计算得到的结果将必定准确
    • 然而,即使得到的是估算值,但此结果对源的操作还是很有帮助的
      • 例如,可以帮助我们去决定是否分块,或者并行遍历
  • 尽管并行算法中有一系列显著的实用功能,但 Spliterator 并不要求确保线程安全;与之相对,使用了 Spliterator 的并行算法的实现,则需要确保同一时间只能有一个线程访问 Spliterator
    • 这个普遍容易地通过 `serial thread-confinement` 方式去实现
    • 一个线程若调用 trySplit(),则会将 Spliterator 转交到另一个线程,另一个线程可能会遍历或进一步分割此 Spliterator
    • 当2个或多个线程同时操作同一个 Spliterator 时,分割或遍历的行为是不确定的
    • 如果原始线程需要将 Spliterator 交由其他线程去处理,那么移交操作最好发生在所有元素都通过 tryAdvance(Consumer) 方法消费完成之前完成,因为某些属性?仅在遍历开始之前进行校验(如 estimateSize() 方法的准确度)
  • 原生的特化 Spliterator 的子类型提供了 OfInt、OfLong、OfDouble
    • Spliterator 子类型的默认实现会通过 tryAdvance(Consumer) 与 forEachRemaining(Consumer),将原生类型的值包装成对应的包装类
    • 这个包装过程会削弱部分性能上的优势
    • 为了避免包装,应该使用基于原生值的方法,例如
      • 相对于 Spliterator.OfInt.tryAdvance(Consumer),应该更优先考虑 Spliterator.OfInt.tryAdvance(IntConsumer)
      • 使用了基于装箱方法的原生值的遍历,并不会影响值顺序
  • apiNote
    • Spliterator 与 Iterator 一样,用于遍历源当中的元素
      • Spliterator API 设计成支持分解,以及单元素迭代,使其除了支持串行操作外,还支持高效的并行操作
      • 此外,通过 Spliterator 去访问元素的协议,使其操作每一个元素时所消耗的系统资源比使用 Iterator 更小,并且避免了使用 next() 与 hasNext() 时出现的资源的竞争(并发情况)
    • 对于可变的源,在 Spliterator 绑定到数据源到遍历结束期间,若出现因元素的添加、替换或删除而导致的数据源结构变化,将有可能出现任意的以及不确定的行为
      • 比如说:这种修改会在使用 `java.util.stream` 框架时生成任意的、不确定的结果
    • 一个源的结构上的修改可以通过如下几种方式进行管理
      • 源的结构不能被修改
        • 例如:`java.util.concurrent.CopyOnWriteArrayList` 是一个不可变的源,通过此源创建的 Spliterator 会返回一个 `IMMUTABLE` 特性值
      • 源本身去管理并发
        • 例如:java.util.concurrent.ConcurrentHashMap 的 key 的集合是一个并发的源,通过此源创建的 Spliterator 会返回一个 CONCURRENT 特性值
      • 可变的源提供一种“延迟绑定”并且“快速失败”的 Spliterator
        • 当修改会影响到计算时,延迟绑定会将窗口收窄
        • 快速失败检测,当源已经开始遍历后,若检测到结构上的修改时,将会抛出 ConcurrentModificationException
        • 例如:ArrayList 以及其他“非并发”的集合,都会提供一种“延迟绑定”并且“快速失败”的 Spliterator
      • 可变的源提供一种“非延迟绑定”并且“快速失败”的 Spliterator
        • 源会增加抛出 `ConcurrentModificationException` 的可能性,因为潜在的修改的时间窗口被放大了
      • 可变的源提供一种“延迟绑定”并且“非快速失败”的 Spliterator
        • 当开始遍历后,数据源会有出现任意的以及不确定的行为的风险,以为修改是不确定的
      • 可变的源提供一种“非延迟绑定”并且“非快速失败”的 Spliterator
        • 源会增加出现任意的以及不确定的行为的风险,因为构造后会出现不确定的修改动作
  • implNote
    • 如果 boolean 系统值 org.openjdk.java.util.stream.tripwire 设置成 true,当操作原生特化子类型时进行了装箱操作时,系统会报出诊断警告信息

接口方法

    • boolean tryAdvance(Consumer<? super T> action)
      • 如果存在剩余元素,则会对其执行给定的动作,同时返回 true,否则返回 false
      • 若 Spliterator 具有 `ORDER` 特性,则动作会以指定顺序执行
      • 由动作抛出的任何异常,都将会传递给调用者
    • default void forEachRemaining(Consumer<? super T> action)
      • 针对每一个剩余的元素都去执行给定的动作,当前线程以串行的方式执行,直到所有元素均被执行,或者动作抛出异常
      • 若 Spliterator 具有 ORDER 特性,则动作会以指定顺序执行
      • 由动作抛出的任何异常,都将会传递给调用者
      • 默认的实现会重复调用 tryAdvance() ,直到返回 false,在必要的情况下,需要被重写
    • Spliterator<T> trySplit()
      • 如果此 Spliterator 可以被分割,则会返回一个包含部分元素的 Spliterator,然后从此方法返回,从此方法返回的 Spliterator 中的元素,将从当前 Spliterator 中分离出去
      • 若当前的 Spliterator 具有 ORDERED 特性,那么返回出去的 Spliterator 也必须具有 ORDERED 特性
      • 除非当前的 Spliterator 涵盖了无限的元素,否则,重复调用 trySplit() 最终一定会返回 null
      • 当返回值非空时
        • 在分割之前,estimateSize() 获得的值,必须大于或等于分割后的两个 Spliterator 用 estimateSize() 获得的值
        • 并且,若当前 Spliterator 具有 `SUBSIZE` 特性,则使用 estimateSize() 获得的值,必须等于分割后两个 Spliterator 用 estimateSize() 获得的值之和
      • 对于某些原因,此方法会返回 null:没元素、遍历已经开始、数据结构存在限制、效率上的一些考量
      • apiNote
      • trySplit()` 在理想情况下(没有遍历),会将元素平均分成两半,允许平衡并行计算
      • 很多背离了此理想状态的情况仍然能够保持高效率
        • 例如:近似切割一个近似平衡的树,或者对于一棵叶子结点可能包含一个或两个元素的树,无法进行进一步的分割
        • 然而,平衡型较差的分割将会导致并行效率的急剧下降
    • long estimateSize()
      • 返回会被 forEachRemaining 操作的元素的数量的估算值,若元素是无限的、未知的、或者是计算成本过高的,会返回一个最大值(MAX_VALUE)
      • 如果一个 Spliterator 具有 SIZED 特性、并且未被遍历或分割,或者此 Spliterator 具有 SUBSIZED 特性、并且未被分割遍历,那么返回的一定是在一次完整遍历中遇到的所有元素数量的精确的值
      • 否则,此估算值则会是不精确的,但是一定会随着 `trySplit()` 的调用而越来越小
      • apiNote
      • 尽管估算值不准确,但它往往是有用的,并且计算成本不高
      • 例如:一个近似平衡的二叉树的一个 sub-spliterator,返回的估算值会是其父节点的一半;如果根 Spliterator 没有维护一个精确的值,那么它将会根据最大深度,通过2的指数次方计算根节点的估算值
posted @ 2019-09-15 11:33  飞蛇在水  阅读(792)  评论(0编辑  收藏  举报