Spliterator 接口


文檔說明

  • 一個用於對一個源當中的元素進行遍歷和分區的對象
  • 一個 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的指數次方計算根節點的估算值


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM