Java 遍历HashMap并remove、put

  1. package net.nie.test;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Iterator;  
  5. import java.util.Map;  
  6.   
  7. public class HashMapTest {  
  8.    private static Map<Integer, String> map=new HashMap<Integer,String>();  
  9.       
  10.    /**  1.HashMap 类映射不保证顺序;某些映射可明确保证其顺序: TreeMap 类 
  11.     *   2.在遍历Map过程中,不能用map.put(key,newVal),map.remove(key)来修改和删除元素, 
  12.     *   会引发 并发修改异常,可以通过迭代器的remove(): 
  13.     *   从迭代器指向的 collection 中移除当前迭代元素 
  14.     *   来达到删除访问中的元素的目的。   
  15.     *   */   
  16.    public static void main(String[] args) {  
  17.         map.put(1,"one");  
  18.         map.put(2,"two");  
  19.         map.put(3,"three");  
  20.         map.put(4,"four");  
  21.         map.put(5,"five");  
  22.         map.put(6,"six");  
  23.         map.put(7,"seven");  
  24.         map.put(8,"eight");  
  25.         map.put(5,"five");  
  26.         map.put(9,"nine");  
  27.         map.put(10,"ten");  
  28.         Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();  
  29.         while(it.hasNext()){  
  30.             Map.Entry<Integer, String> entry=it.next();  
  31.             int key=entry.getKey();  
  32.             if(key%2==1){  
  33.                 System.out.println("delete this: "+key+" = "+key);  
  34.                 //map.put(key, "奇数");   //ConcurrentModificationException  
  35.                 //map.remove(key);      //ConcurrentModificationException  
  36.                 it.remove();        //OK   
  37.             }  
  38.         }  
  39.         //遍历当前的map;这种新的for循环无法修改map内容,因为不通过迭代器。  
  40.         System.out.println("-------\n\t最终的map的元素遍历:");  
  41.         for(Map.Entry<Integer, String> entry:map.entrySet()){  
  42.             int k=entry.getKey();  
  43.             String v=entry.getValue();  
  44.             System.out.println(k+" = "+v);  
  45.         }  
  46.     }  
  47. }  

只能通过iterator的remove实现删除和增加。

分析原因

其实上面的三种遍历方式从根本上讲都是使用的迭代器,之所以出现不同的结果是由于remove操作的实现不同决定的。

首先前两种方法都在调用nextEntry方法的同一个地方抛出了异常

 

1

2

3

4

5

6

7

    final Entry<K,V> nextEntry() {

            if (modCount != expectedModCount)

                throw new ConcurrentModificationException();

            Entry<K,V> e = next;

            ...

            ...

     }

这里modCount是表示map中的元素被修改了几次(在移除,新加元素时此值都会自增),而expectedModCount是表示期望的修改次数,在迭代器构造的时候这两个值是相等,如果在遍历过程中这两个值出现了不同步就会抛出ConcurrentModificationException异常。

1、HashMap的remove方法实现

 

1

2

3

4

public V remove(Object key) {

    Entry<K,V> e = removeEntryForKey(key);

    return (e == null ? null : e.value);

}

2、HashMap.KeySet的remove方法实现

 

 
  1. public boolean remove(Object o) {

  2. return HashMap.this.removeEntryForKey(o) != null;

  3. }


 

 

 

3、HashMap.HashIterator的remove方法实现

 

1

2

3

4

5

6

7

8

9

10

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;

}

以上三种实现方式都通过调用HashMap.removeEntryForKey方法来实现删除key的操作。在removeEntryForKey方法内只要移除了key modCount就会执行一次自增操作,此时modCount就与expectedModCount不一致了,上面三种remove实现中,只有第三种iterator的remove方法在调用完removeEntryForKey方法后同步了expectedModCount值与modCount相同,所以在遍历下个元素调用nextEntry方法时,iterator方式不会抛异常。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

    final Entry<K,V> removeEntryForKey(Object key) {

        int hash = (key == null) ? 0 : hash(key.hashCode());

        int i = indexFor(hash, table.length);

        Entry<K,V> prev = table[i];

        Entry<K,V> e = prev;

 

        while (e != null) {

            Entry<K,V> next = e.next;

            Object k;

            if (e.hash == hash &&

                ((k = e.key) == key || (key != null && key.equals(k)))) {

                modCount++;

                size--;

                if (prev == e)

                    table[i] = next;

                else

                    prev.next = next;

                e.recordRemoval(this);

                return e;

            }

            prev = e;

            e = next;

        }

 

        return e;

    }

 

发散

1、如果是遍历过程中增加或修改数据呢?
增加或修改数据只能通过Map的put方法实现,在遍历过程中修改数据可以,但如果增加新key就会在下次循环时抛异常,因为在添加新key时modCount也会自增。

2、有些集合类也有同样的遍历问题,如ArrayList,通过Iterator方式可正确遍历完成remove操作,直接调用list的remove方法就会抛异常。

 

 
  1. //会抛ConcurrentModificationException异常

  2. for(String str : list){

  3. list.remove(str);

  4. }

  5.  
  6. //正确遍历移除方式

  7. Iterator<String> it = list.iterator();

  8. while(it.hasNext()){

  9. it.next();

  10. it.remove();

  11. }


 

 

 

 

3、jdk为什么这样设计,只允许通过iterator进行remove操作?
HashMap和keySet的remove方法都可以通过传递key参数删除任意的元素,而iterator只能删除当前元素(current),一旦删除的元素是iterator对象中next所正在引用的,如果没有通过modCount、 expectedModCount的比较实现快速失败抛出异常,下次循环该元素将成为current指向,此时iterator就遍历了一个已移除的过期数据

 

转载自https://blog.csdn.net/e01014165/article/details/52174520

https://blog.csdn.net/weikzhao0521/article/details/53022803?locationNum=10&fps=1

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值