天天看點

HashMap之如何正确周遊并删除元素

HashMap之周遊

HashMap的周遊主要有兩種方式:

第一種采用的是foreach模式,适用于不需要修改HashMap内元素的周遊,隻需要擷取元素的鍵/值的情況。

HashMap<K, V> myHashMap;
for (Map.entry<K, V> item : myHashMap.entrySet()){
    K key = item.getKey();
    V val = item.getValue();
    //todo with key and val
    //WARNING: DO NOT CHANGE key AND val IF YOU WANT TO REMOVE ITEMS LATER
}      

第二種采用疊代器周遊,不僅适用于HashMap,對其它類型的容器同樣适用。

采用這種方法的周遊,可以用下文提及的方式安全地對HashMap内的元素進行修改,并不會對後續的删除操作造成影響。

for (Iterator<Map.entry<K, V>> it = myHashMap.entrySet().iterator; it.hasNext();){
    Map.Entry<K, V> item = it.next();
    K key = item.getKey();
    V val = item.getValue();
    //todo with key and val
    //you may remove this item using  "it.remove();"
}      

HashMap之删除元素

如果采用第一種的周遊方法删除HashMap中的元素,Java很有可能會在運作時抛出異常

HashMap<String, Integer> myHashMap = new HashMap<>();
myHashMap.put("1", 1);
myHashMap.put("2", 2);
for (Map.Entry<String, Integer> item : myHashMap.entrySet()){
    myHashMap.remove(item.getKey());
}
for (Map.Entry<String, Integer> item : myHashMap.entrySet()){
    System.out.println(item.getKey());
}      

運作上面的代碼,Java抛出了 ​

​java.util.ConcurrentModificationException​

​ 的異常。并附有如下資訊。

at java.util.HashMap$HashIterator.nextNode(Unknown Source)
at java.util.HashMap$EntryIterator.next(Unknown Source)
at java.util.HashMap$EntryIterator.next(Unknown Source)      

java.util.ConcurrentModificationException異常說明

hashMap的中有一個域modCount,每次對集合進行修改(增添元素,删除元素……)時都會modCount++

疊代HashMap的HashIterator中有一個變量expectedModCount,該變量會初始化和modCount相等,但如果接下來如果集合進行修改modCount改變,就會造成expectedModCount!=modCount,此時就會抛出java.util.ConcurrentModificationException異常

過程類似下圖(與ArrayList類似)

HashMap之如何正确周遊并删除元素

是以,我們改用第二種周遊方式。

代碼如下:

for (Iterator<Map.Entry<String, Integer>> it = myHashMap.entrySet().iterator(); it.hasNext();){
    Map.Entry<String, Integer> item = it.next();
    //... todo with item
    it.remove();
}
for (Map.Entry<String, Integer> item : myHashMap.entrySet()){
    System.out.println(item.getKey());
}      

在HashMap的周遊中删除元素的特殊情況

上述方法可能足以應付多數的情況,但是如果你的HashMap中的鍵值同樣是一個HashMap,假設你需要處理的是 ​

​HashMap<HashMap<String, Integer>, Double> myHashMap​

​ 時,很不碰巧,你可能需要修改myHashMap中的一個項的鍵值HashMap中的某些元素,之後再将其删除。

這時,單單依靠疊代器的 ​

​remove()​

​ 方法是不足以将該元素删除的。

例子如下:

HashMap<HashMap<String, Integer>, Integer> myHashMap = new HashMap<>();
HashMap<String, Integer> temp = new HashMap<>();
temp.put("1", 1);
temp.put("2", 2);
myHashMap.put(temp, 3);
for (Iterator<Map.Entry<HashMap<String, Integer>, Integer>> 
    it = myHashMap.entrySet().iterator(); it.hasNext();){
    Map.Entry<HashMap<String, Integer>, Integer> item = it.next();
    item.getKey().remove("1");
    System.out.println(myHashMap.size());
    it.remove();
    System.out.println(myHashMap.size());
}      

結果如下:

1
1      

雖然 ​

​it.remove();​

​ 被執行,但是并沒有真正删除元素。

原因在于期望删除的元素的鍵值(即 HashMap<String, Integer> temp )被修改過了。

解決方案:

既然在這種情況下,HashMap中被修改過的元素不能被删除,那麼不妨直接把待修改的元素直接删除,再将原本所需要的“修改過”的元素加入HashMap。

想法很好,代碼如下:

for (Iterator<Map.Entry<HashMap<String, Integer>, Integer>> 
    it = myHashMap.entrySet().iterator(); it.hasNext();){
    Map.Entry<HashMap<String, Integer>, Integer> item = it.next();
    //item.getKey().remove("1");
    HashMap<String, Integer> to_put = new HashMap<>(item.getKey());
    to_put.remove("1");
    myHashMap.put(to_put, item.getValue());
    System.out.println(myHashMap.size());
    it.remove();
    System.out.println(myHashMap.size());
}      

但是依然是RE:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.remove(Unknown Source)      

原因在于,疊代器周遊時,每一次調用 next() 函數,至多隻能對容器修改一次。上面的代碼則進行了兩次修改:一次添加,一次删除。

既然 ​

​java.util.ConcurrentModificationException​

​ 異常被抛出了,那麼去想辦法拿掉這個異常即可。

2
1