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ā)修改異常。