1. 程式人生 > >後端---Java中Iterator(迭代器)原理分析

後端---Java中Iterator(迭代器)原理分析

Java中Iterator(迭代器)的用法及其背後機制的探究

一.背景延伸 

在Java中遍歷List時會用到Java提供的Iterator,Iterator十分好用,原因是:

迭代器是一種設計模式,它是一個物件,它可以遍歷並選擇序列中的物件,而開發人員不需要了解該序列的底層結構。迭代器通常被稱為“輕量級”物件,因為建立它的代價小。

  Java中的Iterator功能比較簡單,並且只能單向移動:

  (1) 使用方法iterator()要求容器返回一個Iterator。第一次呼叫Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法是java.lang.Iterable介面,被Collection繼承。

  (2) 使用next()獲得序列中的下一個元素。

  (3) 使用hasNext()檢查序列中是否還有元素。

  (4) 使用remove()將迭代器新返回的元素刪除。

二.為什麼使用迭代器?

因為最初的時候你用for迴圈遍歷List,之後人家又要你遍歷Set,但是for迴圈無法遍歷Set,因為Set是無序的(無法get值),所以後面就統一用迭代器遍歷集合了。

三.迭代器迭代順序

HashMap按鍵的順序,HashSet按hashCode的順序。

四.Iterator的使用 

(一)Iterator在Collection介面中的使用。

   雖然Collection介面的相關類實現了get()方法,但將Iterator用在它們身上仍然是合適的,下面以ArrayList為例,討論Iterator在Collection中的兩中使用方法:

 1.配合while()迴圈實現遍歷輸出:

1 ArrayList list = new ArrayList();
2 //此處省略list的具體賦值過程
3 Iterator it = list.iterator();
4         while(it.hasNext()){
5             System.out.println(it.next());
6         }

while()中的判斷條件it.hasNext()用於判斷it中是否還有下一元素,有的話就繼續迴圈,輸出語句中的it.next()既可以使“指標”往後走一位,又能將當前的元素返回,用於輸出。

不過上面的是在一般while()迴圈中的使用,我們還可以用for each 迴圈來代替Iterator,因為for each 本身就相當於一個迭代器了:

1 ArrayList list = new ArrayList();
2 //此處同樣省略list的賦值過程
3 for(Object array:list){
4            System.out.println(array);
5 }

 

需要注意的是for each 不適合用來增刪元素,如果單純用來遍歷元素,這種寫法也許會更簡潔一點。

(二)Iterator在Map介面中的使用:

下文用HashMap為例,討論迭代器的兩種主要使用方法。

1.與while()的結合

HashMap<K,V> myMap = new HashMap<K,V>();
//省略myMap的的賦值過程
Iterator<Map.Entry<K,V> it=myMap.entrySet().iterator();
while(it.hasNext()){
        System.out.println(it.next());
}

//如果想讓輸出更加格式化,可以自己重寫toString()方法,由於toString方法的重寫不是本文討論的重點,所以暫且不討論。

Map介面下的iterator應用方法與Collection中的略微有些不同,用到了entrySet()方法,entrySet()用來返回整個鍵—值對。

同樣這裡再貼出for each代替Iterator的用法:

1 HashMap<K,V> myMap=new HashMap<K,V>();
2 //省略myMap賦值過程
3 for(Object oj:myMap.entrySet()){
4             System.out.println(oj);
5 }

五.Iterator的原理 

 所有Iterator都最終實現介面Iterator,Iterator介面中包含三個基本方法,next(), hasNext(), remove(),其中對於List的遍歷刪除只能用Iterator的remove方法;JDK1.8中Iterator介面的原始碼如下:

public interface Iterator<E> {

    boolean hasNext();

    // JDK1.8的新特性,可以通過default在介面中寫個方法的實現
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

下面將基於ArrayList的Iterator的實現分析Iterator的原理(基於JDK1.8):

在ArrayList類中有個方法iterator(),此方法將返回一個iterator的實現,這裡可以看出實現類叫Itr,通過其它原始碼可知,此類是AarryList的內部類,即ArryList的Iterator實現在ArrayList內部;
這時ArrayList返回一個Iterator迭代器的方法:

   public Iterator<E> iterator() {
        return new Itr();
    }

下面重點看下ArrayList中實現類Itr類的原始碼:

private class Itr implements Iterator<E> {
        /**
         * 下一個返回的位置
         */
        int cursor = 0;

        /**
         * 當前操作的位置
         */
        int lastRet = -1;

        /**
         * 類似版本號,檢查List是否有更新
         */
        int expectedModCount = modCount;

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

        public E next() {  // 返回下一個元素
            checkForComodification();
            try {
                int i = cursor;     // cursor記錄的是下一個元素,所以呼叫next時將返回的是cursor對應的元素
                E next = get(i);    //  記錄需要返回的元素
                lastRet = i;        // 記錄當前元素
                cursor = i + 1;     // 記錄下一個元素
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {       // 移除元素
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();     //   檢查是否有更改,remove或者add

            try {
                AbstractList.this.remove(lastRet);   // 刪除當前元素  
                if (lastRet < cursor)                // 刪除了之後指標減1
                    cursor--;
                lastRet = -1;                    
                expectedModCount = modCount;         // 保持版本號一致
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {    // 如果有更改則丟擲ConcurrentModificationException異常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

從上面程式碼中可以看出,對於Iterator的實現中主要有幾個變數cursor,lastRest, expectedModCount三個變數,其中cursor將記錄下一個位置,lastRet記錄當前位置,expectedModCount記錄沒有修改的List的版本號。

問題:還記得說List中在iterator遍歷的時候,不能隨便新增和刪除元素嗎,這是為什麼呢?

在iterator遍歷的時候丟擲異常都是checkForComodification作的,根本原因是modCout和expectedModCount不相等,導致丟擲異常

那為啥會不相等呢?

可以看看ArrayList的add和remove方法,
 

è¿éåå¾çæè¿°

 

 add方法:

è¿éåå¾çæè¿°

從上面的程式碼中可以看出只要對ArrayList作了新增或刪除操作都會增加modCount版本號,這樣的意思是在迭代期間,會不斷檢查modCount和迭代器持有的expectedModCount兩者是不是相等,如果不想的就丟擲異常了。

這樣在迭代器迭代期間不能對ArrayList作任何增刪操作,但是可以通過iterator的remove作刪除操作,從之前的程式碼可以看出,在iterator的remove()中有一行程式碼,expectedModCount = modCount; 這個賦值操作保證了iterator的remove是可用性的。

當然,iterator期間不能增刪的根本原因是ArrayList遍歷會不準,就像遍歷陣列的時候改變了陣列的長度一樣
 

 

參考博文:

https://blog.csdn.net/qq_36101933/article/details/82632137

https://www.cnblogs.com/TangMoon/p/7589082.html

https://blog.csdn.net/qq_36470686/article/details/85065545