Iterator迭代器使用注意事項

1、Iterator迭代器

我們在平常經(jīng)常會使用到foreach,for關(guān)鍵字,其實他們的內(nèi)部原理使用的都是Iterator迭代器的原理。
但是在使用的時候需要注意的是,如果在遍歷的過程中增加元素、刪除元素等改變了List、HashMap之類的List的結(jié)構(gòu)時,會產(chǎn)生ConcurrentModificationException(并發(fā)修改)異常。

2、分析

我們使用HashMap來分析,先看下面的一段代碼:

HashMap<String, String> map = new HashMap<>();
map.put("1", "111");
map.put("2", "222");
map.put("3", "333);
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while(iterator.hasNext()) {
    Map.Entry<String, String> entry = iterator.next(); //這里會引發(fā)ConcurrentModificationException異常
    String key = entry.getKey();
    String value = entry.getValue();
    if(value.equals("111") {
        map.remove(key); 
        //map.put("444");
    }
}

從引發(fā)異常的代碼行跟蹤進去,進入到HashMap的HashIterator類,該類實現(xiàn)了Iterator接口。而next()方法最終會執(zhí)行到nextEntry()方法??匆幌耼extEntry()方法的實現(xiàn):

final Entry<K,V> nextEntry() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    HashMapEntry<K,V> e = next;
    if (e == null)
        throw new NoSuchElementException();

    if ((next = e.next) == null) {
        HashMapEntry[] t = table;
        while (index < t.length && (next = t[index++]) == null)
            ;
    }
    current = e;
    return e;
}

可以看到拋出異常的原因是modCount != expectedModCount。modCount是HashMap中的一個變量,當HashMap結(jié)構(gòu)改變時(比如put進去了一個新的元素,刪除了一個新的元素等),這個modCount記錄的是改變的次數(shù),而expectedModCount是HashIterator類對象的一個變量,在HashIterator構(gòu)造函數(shù)中賦值,如下所示:

HashIterator() {
    expectedModCount = modCount;
    if (size > 0) { // advance to first entry
        HashMapEntry[] t = table;
        while (index < t.length && (next = t[index++]) == null)
            ;
    }
}

上面的expectedModCount = modCount即為賦值語句。
返回上面舉的例子,當我們在遍歷HashMap時刪除了一個元素,即map.remove(key); 最終執(zhí)行removeEntryForKey(key)方法,在該方法中執(zhí)行了modCount++,也即modCount的值改變了。當在HashIterator中繼續(xù)往下執(zhí)行到nextEntry()方法時,由于modCount的值不等于expectedModCount,那么就拋出了ConcurrentModificationException異常。

3、為什么不相等就拋出異常

我們發(fā)散一下,如果將if (modCount != expectedModCount)這句判斷語句去掉呢?
來看一種情況,還是用上面的remove(key)作為例子,如果這個key剛好是下一個需要訪問到的key呢?順著nextEntry()看下來:

final Entry<K,V> nextEntry() {
        ...省略
    HashMapEntry<K,V> e = next;
        ...省略
    if ((next = e.next) == null) {
        HashMapEntry[] t = table;
        while (index < t.length && (next = t[index++]) == null)
            ;
    }
    current = e;
    return e;
}

我們將異常的情況去掉。這個時候next就是我們之前刪除掉的entry,這個entry.next為空,進入if語句塊,if語句塊要做的事情是,從index開始尋找下一個不為空的元素。而index的值是entry還沒有被刪除時所處的位置。說起來聽抽象的,還是看圖說話好了:



例如,我們在遍歷的時候刪除了3號的元素,這個時候index指向了下一個元素,即index=3。當我們繼續(xù)執(zhí)行nextEntry時,由于hasMap改變了,也即table改變了,那么下次訪問到就是5號的元素,也就是說4號元素完全沒有被我們訪問到,所以這是有問題的。所以Java規(guī)定了如果HashMap的結(jié)構(gòu)發(fā)生了變化,那么就拋出并發(fā)修改異常。

4、怎么在遍歷時增加或者刪除元素?

上面分析了既然在遍歷時不允許刪除HashMap的元素,那么我們有什么樣的方法刪除或者添加嗎?因為我們在工作時肯定會遇到這樣的問題的。
對于刪除,我們可以看到Iterator有一個remove()的方法。而HashIterator的remove()方法如下:

public void remove() {
    if (current == null)
        throw new IllegalStateException();
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    Object k = current.key;
    current = null;
    HashMap.this.removeEntryForKey(k);
    expectedModCount = modCount;
}

可以看到在這里通過調(diào)用HashMap的removeEntryForKey刪除了當前的元素,并且同步地將expectedModCount修改為modCount,所以下次執(zhí)行nextEntry()方法時就不會報并發(fā)修改異常了。

上面的情況只是在單線程不會出問題,但是如果在多線程下,即使使用了remove()方法,也會有可能出現(xiàn)ConcurrentModificationException錯誤。所以在多線程下為了保證現(xiàn)場安全,我們需要對要操作的HashMap進行一個加鎖操作,這樣就可以防止在遍歷的過程中有其他現(xiàn)場去修改HashMap的結(jié)構(gòu),從而導致出現(xiàn)ConcurrentModificationException錯誤。

那么如果我們想要添加元素呢?好像Iterator只實現(xiàn)了remove()這樣一個方法,對于其他操作并沒有為我們實現(xiàn),那么我們就需要自己來實現(xiàn)了:

    LinkedList<Map.Entry<String, String>> tempList = new LinkedList<Map.Entry<String, String>>();
    tempList.addAll(map.entrySet());
    ListIterator<Map.Entry<String, String>> itor = tempList.listIterator();
    Map.Entry entry = null;

    while (itor.hasNext()) {
         entry = (Map.Entry) itor.next();
         Object key = entry.getKey();

         if (key.toString().equals("3")) {
            map.put("33", "33");
         }
    }

我們可以使用一個LinkedList來裝載HashMap的entrySet,然后在遍歷時修改或者添加map的元素,由于該LinkedList的Iterator和HashMap的Iterator是不同的對象,所以不用擔心會引發(fā)并發(fā)修改異常。

5、文檔參考:

http://www.cnblogs.com/Scott007/p/3560255.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容