1. 程式人生 > >java原始碼閱讀之Spliterator

java原始碼閱讀之Spliterator

Spliterator是java 8才開始提供的一個迭代器實現,從名稱可以看出來,Spliterator是一個可分割的迭代器,用來分割和迭代給定源的元素,這裡的源可以是collection,array和io等。
spliterator的特別之處在於,它可以通過trySplit()方法,將spliterator例項分割成多個較小的spliterator例項,方便多執行緒併發處理。

spliterator還可以通過方法

int characteristics();

返回當前例項的一個特徵集,這個特徵值集包括:

//表示迭代器需要按照其原始順序迭代其中元素的
public static
final int ORDERED = 0x00000010; //迭代器中的元素是沒有重複的 public static final int DISTINCT = 0x00000001; //迭代器是按照某種方式排序的順序迭代其中元素的 public static final int SORTED = 0x00000004; //表示迭代器將要迭代的元素的個數是可計數的,有界的 public static final int SIZED = 0x00000040; //迭代器迭代的元素是沒有值為`null`的 public static final int NONNULL = 0x00000100; //迭代器迭代的元素是不可改變的,也不可以增加、替換和刪除
public static final int IMMUTABLE = 0x00000400; //表示迭代器的資料來源是執行緒安全的 public static final int CONCURRENT = 0x00001000; //表示當前迭代器所有的子迭代器(直接的或者間接的),都是`SIZED`和`SUBSIZED`的 public static final int SUBSIZED = 0x00004000;

例如一個Collection例項的spliterator會報告一個SIZED的特徵,一個Set例項會報告一個DISTINCT特徵,等。

如果一個迭代器沒有報告IMMUTABLE或者CONCURRENT特徵時,其繫結資料來源後會檢查資料來源的結構化改動。後繫結的迭代器是繫結資料來源發生在其第一次遍歷、第一次拆分、第一次查詢大小時,而不是在迭代器建立後立刻繫結資料來源。和其它迭代器一樣,繫結前對源的修改可以反應在迭代器遍歷過程中,而繫結後的對源的修改會導致ConcurrentModificationException

異常。
Spliterator的批量遍歷方法(forEachRemaining())可以遍歷所有元素之後優化遍歷並檢對源的結構化修改,而不是逐個檢查元素並立即丟擲失敗。
Spliterator提供了estimateSize()方法用來估算迭代器中剩餘元素的數量,理想情況下,它能夠準確的計算迭代器中能夠成功迭代的元素的數量,即使是在不準切的時候,其結果依然能夠為對迭代器的下一步操作,如是直接迭代剩餘元素還是繼續分割迭代器提供依據。
儘管Spliterator為併發迭代提供了入口,但它依舊是執行緒不安全的。相反,spliterator的併發實現必須保證其在同一個時刻只有一個執行緒訪問。呼叫trySplit()的執行緒可能會後續將迭代器交給其他執行緒進行迭代或者繼續分割,併發的分割或者迭代同一個迭代器會導致不確定的結果。如果需要將迭代器由一個執行緒遞交給另一個執行緒,需要保證遞交前沒有使用tryAdvance()消費任何元素。

Spliterator為原始型別中的int、long、double提供了專門的子類並預設實現了tryAdvance(java.util.function.Consumer)forEachRemaining(java.util.function.Consumer)

方法

estimateSize()

估計當前Spliterator例項中將要迭代的元素的數量,如果數量是無限的、未知的或者計算數量的花銷太大,則返回Long.MAX_VALUE

trySplit()

分割迭代器,沒呼叫一次,將原來的迭代器等分為兩份,並返回索引靠前的那一個子迭代器。

forEachRemaining(Consumer<? super E> action)

通過action批量消費所有的未迭代的資料。

實現樣例

以ArrayListSpliterator為例說明Spliterator的實現:

static final class ArrayListSpliterator<E> implements Spliterator<E> {
    //ArrayList資料來源的物件引用
    private final ArrayList<E> list;
    private int index; // 起始位置所有,會被advance()和split()函式修改。
    private int fence; // 結束位置,-1表示到最後一個元素
    private int expectedModCount; // initialized when fence set

    ArrayListSpliterator(ArrayList<E> list, int origin, int fence, int expectedModCount) {
        this.list = list; // OK if null unless traversed
        this.index = origin;
        this.fence = fence;
        this.expectedModCount = expectedModCount;
    }

    private int getFence() { // initialize fence to size on first use
        int hi; // (a specialized variant appears in method forEach)
        ArrayList<E> lst;
        if ((hi = fence) < 0) {
            if ((lst = list) == null)
                hi = fence = 0;
            else {
                expectedModCount = lst.modCount;
                hi = fence = lst.size;
            }
        }
        return hi;
    }

    // 每次分割,都將原來的迭代器等分為兩個,並返回索引靠前的那個
    public ArrayListSpliterator<E> trySplit() {
        int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
        return (lo >= mid) ? null : new ArrayListSpliterator<E>(list, lo, index = mid, expectedModCount); // divide range in half unless too small
    }

    // 使用action消費下一個元素
    public boolean tryAdvance(Consumer<? super E> action) {
        if (action == null)
            throw new NullPointerException();
        int hi = getFence(), i = index;
        if (i < hi) {
            index = i + 1;
            @SuppressWarnings("unchecked") 
            E e = (E)list.elementData[i];
            action.accept(e);
            if (list.modCount != expectedModCount)
                throw new ConcurrentModificationException();
            return true;
        }
        return false;
    }

    // 批量消費所有未迭代的元素
    public void forEachRemaining(Consumer<? super E> action) {
        int i, hi, mc; // hoist accesses and checks from loop
        ArrayList<E> lst; Object[] a;
        if (action == null)
            throw new NullPointerException();
        if ((lst = list) != null && (a = lst.elementData) != null) {
            if ((hi = fence) < 0) {
                mc = lst.modCount;
                hi = lst.size;
            } else
                mc = expectedModCount;
            if ((i = index) >= 0 && (index = hi) <= a.length) {
                for (; i < hi; ++i) {
                    @SuppressWarnings("unchecked") E e = (E) a[i];
                    action.accept(e);
                }
                if (lst.modCount == mc)
                    return;
            }
        }
        throw new ConcurrentModificationException();
    }

    //估算將要迭代的元素的數量
    public long estimateSize() {
        return (long) (getFence() - index);
    }

    //特徵值,對於ArrayListSpliterator,其迭代順序是按照原始順序迭代的,而且可以預估數量。
    public int characteristics() {
        return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
    }
}

此外,在Spliterators類中,預設為int、long、double等幾種基本型別的集合實現了其Spliterator迭代器。

使用

package com.company;

import java.util.ArrayList;
import java.util.Spliterator;
import java.util.function.Consumer;

public class IteratorTest {
    public static void main(String[] args) {
        ArrayList<String> arrays = new ArrayList<String>();

        arrays.add("a");
        arrays.add("b");
        arrays.add("c");
        arrays.add("d");
        arrays.add("e");
        arrays.add("f");
        arrays.add("g");
        arrays.add("h");
        arrays.add("i");
        arrays.add("j");

        arrays.remove("j");

        Spliterator<String> p = arrays.spliterator();

        Spliterator<String> s1 = p.trySplit();

        Spliterator<String> s2 = p.trySplit();

        System.out.println("p.consume :");
        p.forEachRemaining(new Consumer<String>() {
            public void accept(String s) {
                System.out.println(s);
            }
        });

        System.out.println("s1.consume");
        s1.forEachRemaining(new Consumer<String>() {
            public void accept(String s) {
                System.out.println(s);
            }
        });

        System.out.println("s2.consume");
        s2.forEachRemaining(new Consumer<String>() {
            public void accept(String s) {
                System.out.println(s);
            }
        });
    }
}

結果

這裡寫圖片描述