Java - Map删除元素报ConcurrentModificationException问题解决

Java - Map删除元素报ConcurrentModificationException问题解决

前言

其实我们在遍历Map类型的集合的时候,有时候可能希望删除满足一定条件的key。但是这个时候就会发现,抛异常了。那么本文就以LinkedHashMap为例,来看下原理。

一. 案例复现

我们来复现一下:

public static void main(String[] args) throws Exception {
    LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer, Integer>(16, 0.75F, true) {{
        put(1, 1);
        put(2, 2);
        put(3, 3);
        put(4, 4);
    }};
    for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
        if (entry.getKey() % 2 == 0) {
            System.out.println("To delete key " + entry.getKey());
            map.remove(entry.getKey());
            System.out.println("The key " + +entry.getKey() + " was deleted");
        }
    }
}

结果如下:
在这里插入图片描述

这是因为在遍历的时候,通过remove()函数去删除节点的时候,只会维护modCount这个变量。而expectedModCount变量却不会维护,因此造成两个数据不一致。

二. 原理分析

我在这篇文章中讲了关于remove函数的原理:Java - LinkedHashMap原理分析

里面有这么一段代码:

abstract class HashIterator {
	public final void remove() {
       Node<K,V> p = current;
       	// 理论上不可能删除null,是的抛出异常
        if (p == null)
            throw new IllegalStateException();
        // 高并发下很容易造成这种情况,会抛出异常
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        current = null;
        K key = p.key;
        // 节点删除
        removeNode(hash(key), key, null, false, false);
        expectedModCount = modCount;
    }
}

我们可以看到,关于ConcurrentModificationException异常的抛出,本质原因就是:HashMap中在删除一个节点的时候,只会维护modCount这个变量,而不会维护expectedModCount变量。

  1. 因此当程序在成功的删除一个变量的时候,modCount就会发生改变。
  2. 那么下一次循环的时候,就会发现modCount != expectedModCount。随即抛出异常。

那么我们只需要再删除节点的时候,同时维护这两个变量就可以了。LinkedHashMap中有一个迭代器LinkedHashIterator

abstract class LinkedHashIterator {
    LinkedHashMap.Entry<K,V> next;
    LinkedHashMap.Entry<K,V> current;
    int expectedModCount;

    LinkedHashIterator() {
        next = head;
        expectedModCount = modCount;
        current = null;
    }

    public final boolean hasNext() {
        return next != null;
    }

    final LinkedHashMap.Entry<K,V> nextNode() {
        LinkedHashMap.Entry<K,V> e = next;
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (e == null)
            throw new NoSuchElementException();
        current = e;
        next = e.after;
        return e;
    }

    public final void remove() {
        Node<K,V> p = current;
        if (p == null)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        current = null;
        K key = p.key;
        removeNode(hash(key), key, null, false, false);
        expectedModCount = modCount;
    }
}

我们可以发现,通过迭代器来remove元素的时候,会同时维护上述两个变量。那么我们是不是可以通过迭代器的方式来代替传统的for循环呢?

三. 问题解决

解决:我们通过迭代器去删除,最终代码更改为:

public static void main(String[] args) throws Exception {
    LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer, Integer>(16, 0.75F, true) {{
        put(1, 1);
        put(2, 2);
        put(3, 3);
        put(4, 4);
    }};
    Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
    while (iterator.hasNext()) {
        Map.Entry<Integer, Integer> entry = iterator.next();
        if (entry.getKey() % 2 == 0) {
            System.out.println("To delete key " + entry.getKey());
            iterator.remove();
            System.out.println("The key " + +entry.getKey() + " was deleted");
        }
    }
}

结果如下:
在这里插入图片描述

可以发现,满足条件的元素能够被删除,并且程序不会抛异常。这个问题也就解决了。

备注:当然,在正常开发中需要避免出现这种循环里面删除元素的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值