【java集合梳理】— 淺談iterator接口

一、iterator接口介紹

iterator接口,也是集合大家庭中的一員。和其他的MapCollection接口不同,iterator 主要是爲了方便遍歷集合中的所有元素,用於迭代訪問集合中的元素,相當於定義了遍歷元素的規範,而另外的MapCollection接口主要是定義了存儲元素的規範。
還記得麼?之前說的iterable接口,有一個方法就是叫iterator(),也是返回iterator對象。

迭代:不斷訪問集合中元素的方式,取元素之前先判斷是否有元素,有則取出來,沒有則結束,不斷循環這個過程,直到遍歷完裏面所有的元素。

接口定義的方法如下:

boolean hasNext(); // 是否有下一個元素

E next();   // 獲取下一個元素

// 移除元素
default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    
// 對剩下的所有元素進行處理,action則爲處理的動作,意爲要怎麼處理
default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }

但是值得注意的是,集合類的整體不是繼承了iterator接口,而是繼承了iterable接口,通過iterable接口的方法返回iterator的對象。值得注意的是,iteratorremove()方法,是迭代過程中唯一安全的修改集合的方法,爲何這樣說?
如果使用for循環索引的方式遍歷,刪除掉一個元素之後,集合的元素個數已經變化,很容易出錯。例如

for(int i=0;i<collection.size();i++){
    if(i==2){
        collection.remove(i);
    }
}

iteratorremove()方法則不會出錯,因爲通過調用hasNext()next()方法,對指針控制已經處理得比較完善。

二、爲什麼需要iterator接口

首先,我們知道iterator接口是爲了定義遍歷集合的規範,也是一種抽象,把在不同集合的遍歷方式抽象出來,這樣遍歷的時候,就不需要知道不同集合的內部結構。

爲什麼需要抽象?

假設沒有iterator接口,我們知道,遍歷的時候只能通過索引,比如

for(int i=0;i<array.size();i++){
    T item = array[i];
}

這樣一來,耦合程度比較高,如果使用的數據結構變了,就要換一種寫法,不利於維護已有的代碼。如果沒有iterator,那麼客戶端需要維護指針,相當於下放了權限,會造成一定程度的混亂。抽象則是把遍歷功能抽取出來,交給iterator處理,客戶端處理集合的時候,交給更“專業”的它,it do it well.

三、iterator接口相關接口

3.1 ListIterator

ListIterator繼承於Iterator接口,功能更強大,只能用於訪問各種List類型,使用List類型的對象list,調用listIterator()方法可以獲取到一個指向list開頭的ListIterator

從上面圖片接口看,這個接口具有訪問下一個元素,判斷是否有下一個元素,是否有前面一個元素,判斷是否有前一個元素,獲取下一個元素的索引,獲取上一個元素的索引,移除元素,修改元素,增加元素等功能。和普通的Iterator不一樣的是,ListIterator的訪問指針可以向前或者向後移動,也就是雙向移動。

boolean hasNext();  //是否還有元素 

E next();   //獲取下一個元素

boolean hasPrevious();  //是否有上一個元素

E previous();   // 獲取上一個元素

int nextIndex();    //獲取下一個索引

int previousIndex();    //獲取上一個索引

void remove();  //移除

void set(E e); //更新

void add(E e); //添加元素

測試代碼如下:

        List<String> list =
                new ArrayList<String>(Arrays.asList("Book","Pen","Desk"));
        // 把指針指向第一個元素
        ListIterator<String> lit = list.listIterator(1);
        while(lit.hasNext()){
            System.out.println(lit.next());
        }
        System.out.println("===================================");
        //指針指向最後一個元素列表中的最後一個元素修改ChangeDesk。
        lit.set("ChangeDesk");
        // 往前面遍歷
        while(lit.hasPrevious()){
            System.out.println(lit.previous());
        }

輸出如下:

Pen
Desk
===================================
ChangeDesk
Pen
Book

如果點開ArrayList的源碼,看到與ListIterator相關的部分,我們會發現其實ArrayList在底層實現了一個內部類ListItr,繼承了Itr,實現了ListIterator接口。這個Itr其實就是實現了Iterator,實現了基本的List迭代器功能,而這個ListItr則是增強版的專門爲List實現的迭代器。裏面使用cursor作爲當前的指針(索引),所有函數功能都是操作這個指針實現。

private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            // 設置當前指針 
            cursor = index;
        }

        public boolean hasPrevious() {
            // 不是第一個元素就表明有前一個元素
            return cursor != 0;
        }
        // 獲取下一個元素索引
        public int nextIndex() {
            return cursor;
        }

        // 獲取前面一個元素索引
        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            //檢查是否被修改
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            // 返回前一個元素
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

我們可以看到,在上面方法中,有很多校驗,比如checkForComodification(),意爲檢查是否被修改,list中的元素修改有可能導致數組越界。

3.2 SpitIterator

準確地來說,SpitIteratorIterator並沒有什麼關係,只是兩個功能上有類似。SpitIterator主要是定義類將集合分割成多個集合,方便並行計算。

3.2.1 SpitIterator源碼方法解析

public interface Spliterator<T> {

    // 順序處理每一個元素,參數是處理的動作,如果還有元素需要處理則返回true,否則返回false
    boolean tryAdvance(Consumer<? super T> action);

    // 依次處理剩下的元素
    default void forEachRemaining(Consumer<? super T> action) {
        do { } while (tryAdvance(action));
    }

    // 最重要的方法,用來分割集合
    Spliterator<T> trySplit();
    
    //估算還有多少元素需要遍歷處理
    long estimateSize();

    // 獲取準確的元素,如果不能獲取準確的,則會返回估算的
    default long getExactSizeIfKnown() {
        return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
    }

    // 表示該Spliterator有哪些特性,這個像是個拓展功能,更好控制和優化Spliterator使用
    int characteristics();
    
    // 判斷是否有哪些特性
    default boolean hasCharacteristics(int characteristics) {
        return (characteristics() & characteristics) == characteristics;
    }
    // 如果這個Spliterator的源具有已排序的特徵,那麼這個方法將返回相應的比較器。如果源按自然順序排序,則返回     // null。否則,如果源未排序,則拋出IllegalStateException。
    default Comparator<? super T> getComparator() {
        throw new IllegalStateException();
    }

    public static final int ORDERED    = 0x00000010;
    public static final int DISTINCT   = 0x00000001;
    public static final int SORTED     = 0x00000004;
    public static final int SIZED      = 0x00000040;
    public static final int NONNULL    = 0x00000100;
    public static final int IMMUTABLE  = 0x00000400;
    public static final int CONCURRENT = 0x00001000;
    public static final int SUBSIZED = 0x00004000;
}

使用的方法例子如下:

    public static void spliterator(){
        List<String> list = Arrays.asList("1", "2", "3","4","5","6","7","8","9","10");
        // 獲取可迭代器
        Spliterator<String> spliterator = list.spliterator();
        // 一個一個遍歷
        System.out.println("tryAdvance: ");
        spliterator.tryAdvance(item->System.out.print(item+" "));
        spliterator.tryAdvance(item->System.out.print(item+" "));
        System.out.println("\n-------------------------------------------");

        // 依次遍歷剩下的
        System.out.println("forEachRemaining: ");
        spliterator.forEachRemaining(item->System.out.print(item+" "));
        System.out.println("\n------------------------------------------");

        // spliterator1:0~10
        Spliterator<String> spliterator1 = list.spliterator();
        // spliterator1:6~10 spliterator2:0~5
        Spliterator<String> spliterator2 = spliterator1.trySplit();
        // spliterator1:8~10 spliterator3:6~7
        Spliterator<String> spliterator3 = spliterator1.trySplit();
        System.out.println("spliterator1: ");
        spliterator1.forEachRemaining(item->System.out.print(item+" "));
        System.out.println("\n------------------------------------------");
        System.out.println("spliterator2: ");
        spliterator2.forEachRemaining(item->System.out.print(item+" "));
        System.out.println("\n------------------------------------------");
        System.out.println("spliterator3: ");
        spliterator3.forEachRemaining(item->System.out.print(item+" "));
    }
  • tryAdvance() 一個一個元素進行遍歷
  • forEachRemaining() 順序地分塊遍歷
  • trySplit()進行分區形成另外的 Spliterator,使用在並行操作中,分出來的是前面一半,就是不斷把前面一部分分出來

結果如下:

tryAdvance: 
1 2 
-------------------------------------------
forEachRemaining: 
3 4 5 6 7 8 9 10 
------------------------------------------
spliterator1: 
8 9 10 
------------------------------------------
spliterator2: 
1 2 3 4 5 
------------------------------------------
spliterator3: 
6 7 

還有一些其他的用法在這裏就不列舉了,主要是trySplit()之後,可以用於多線程遍歷。理想的時候,可以平均分成兩半,有利於並行計算,但是不是一定平分的。

3.2.2 SpitIterator裏面哪些特徵常量有什麼用呢?

spliterator可以將其實現特徵表示爲同一接口中定義的一組常量。也就是我們見到的ORDERED,DISTINCT,SORTED,SIZED之類的,這個意思是每一個實現類,都有自己的實現方式,實現方式不同,實現特徵也不一樣,比如ArrayList實現特徵是ORDERED,SIZEDSUBSIZED,這個我們可以通過
characteristics() and hasCharacteristics()來判斷。例如:

    public static void main(String[] args) throws Exception{
        List<String> list = new ArrayList<>();
        Spliterator<String> s = list.spliterator();
        System.out.println(s.characteristics());
        if(s.hasCharacteristics(Spliterator.ORDERED)){
            System.out.println("ORDERED");
        }
        if(s.hasCharacteristics(Spliterator.DISTINCT)){
            System.out.println("DISTINCT");
        }
        if(s.hasCharacteristics(Spliterator.SORTED)){
            System.out.println("SORTED");
        }
        if(s.hasCharacteristics(Spliterator.SIZED)){
            System.out.println("SIZED");
        }

        if(s.hasCharacteristics(Spliterator.CONCURRENT)){
            System.out.println("CONCURRENT");
        }
        if(s.hasCharacteristics(Spliterator.IMMUTABLE)){
            System.out.println("IMMUTABLE");
        }
        if(s.hasCharacteristics(Spliterator.NONNULL)){
            System.out.println("NONNULL");
        }
        if(s.hasCharacteristics(Spliterator.SUBSIZED)){
            System.out.println("SUBSIZED");
        }
    }

輸出的結果是

16464
ORDERED
SIZED
SUBSIZED

輸出結果中的16464和其他的怎麼掛鉤的呢?其實我們發現上面的hasCharacteristics()方法中,實現是return (characteristics() & characteristics) == characteristics;,不難看出,這些狀態是根據與運算來計算出來的。上面的結果也表明ArrayListORDERED,SIZEDSUBSIZED這幾個特徵。
如果是HashSet則特徵是DISTINCTSIZED

四、 iterator在集合中的實現例子

iterator只是一個接口,相當於一個規範,所有的子類或者繼承類實現的時候理論上應該遵守,但是不一樣的繼承類/子類會有不一樣的實現。

4.1 iterator在ArrayList的實現

iterator只是一個接口,一個規範,雖然裏面有個別方法有默認實現,但是最重要也最豐富的的,是它在子類中的實現與拓展,現在來看在ArrayList 中的實現。ArrayList並沒有直接去實現iterator接口,而是通過內部類的方式來操作,內部類爲Itr,

    private class Itr implements Iterator<E> {
        // 下一個元素的索引(指針)
        int cursor;       // index of next element to return
        // 最後一個元素指針索引
        int lastRet = -1; // index of last element returned; -1 if no such
        // 修改次數(版本號)
        int expectedModCount = modCount;

        Itr() {}
        // 是否有下一個元素
        public boolean hasNext() {
            return cursor != size;
        }

        // 下一個元素
        @SuppressWarnings("unchecked")
        public E next() {
            //安全檢查
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        // 移除
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        // 依次處理剩下的元素
        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
        // 安全檢查,檢查是否被修改
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

從上面的源碼可以看到,很多關於被修改的檢查,集合會追蹤修改(增刪改)的次數(modCount 又稱版本號),每一個迭代器會單獨立維護一個計數器,在每次操作(增刪改),檢查版本號是否發生改變,如果改變,就會拋出ConcurrentModificationException() 異常,這是一種安全保護機制。
安全檢查,快速失敗機制實現主要和變量modCountexpectedModCount,以及一個checkForComodification()方法有關,也就是expectedModCount是內部類的修改次數,從字面意思看是指理論上期待的修改次數,modCount是外部類的修改次數,創建的時候,會將modCount賦值給expectedModCount,兩者保持一致,如果在迭代的過程中,外部類的modCount對不上expectedModCount,n那麼就會拋出ConcurrentModificationException異常。

4.2 iterator在HashMap的實現

首先,HashMap裏面定義了一個HashIterator,爲什麼這樣做呢?因爲HashMap存儲結構的特殊性,裏面有Entry<key,value>,所以遍歷就有三種情況,一個是Key,一個是Value,另一個就是Entry,這三個的迭代遍歷都有相似性,所以這裏根據抽象原則,定義了一個Hash迭代器。

    abstract class HashIterator {
        // 下一個節點
        Node<K,V> next;
        
        // 當前節點
        Node<K,V> current;     // current entry
        // 期望修改次數
        int expectedModCount;  // for fast-fail
        // 索引
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { 
                // 指向第一個不爲空的元素
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }

        // 是否有下一個節點
        public final boolean hasNext() {
            return next != null;
        }

        // 獲取下一個節點
        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        // 移除
        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

之後分別定義KeyIterator,ValueIterator,EntryIterator,繼承於HashIterator

    // 遍歷key
    final class KeyIterator extends HashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().key; }
    }
    // 遍歷value
    final class ValueIterator extends HashIterator
        implements Iterator<V> {
        public final V next() { return nextNode().value; }
    }

    //遍歷entry
    final class EntryIterator extends HashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }

五、總結

以上的種種,關於Iterator,其實就是一個迭代器,可簡單地理解爲遍歷使用,主要功能是指向一個節點,向前或者向後移動,如果數據結構複雜就需要多個迭代器,比如HashMap,可以避免多個迭代器之間相互影響。每一個迭代器都會有
expectedModCount 和modCount,就是校驗這個迭代過程中是否被修改,如果修改了,則會拋出異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章