# HashMap边遍历边删除的正确姿势

# 问题代码

public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("123", "123");
        map.put("1231", "123");
        //边遍历边移除的错误写法
        for (String key : map.keySet()) {
            if ("123".equals(key)) {
              //调用map的remove方法
                map.remove(key);
            }
        }
        map.forEach((k, v) -> System.out.println(k + " ---- " + v));
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1469)
	at java.util.HashMap$KeyIterator.next(HashMap.java:1493)
	at com.kusch.ares.aatmp.Test.main(Test.java:15)

Process finished with exit code 1
1
2
3
4
5
6

可见出现了错误ConcurrentModificationException

# 问题报错处源码分析

从上述报错信息可以进到java.util.HashMap$HashIterator.nextNode(HashMap.java:1469)处查看其对应的方法:

final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            //出现在这里,modeCount 和 expectedModCount 不相等了
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }
1
2
3
4
5
6
7
8
9
10
11
12
13

modeCount 用于标识map被修改过了几次,expectedModCount是在你开始迭代遍历集合的时候赋予的值,在上述循环遍历之前,modCount和expectedModCount的数字是相等的,但是一旦在遍历中途进行了removet操作,modCount就会增加,从而导致报错。

看一下remove方法:

    @Override
    public boolean remove(Object key, Object value) {
        return removeNode(hash(key), key, value, true, true) != null;
    }
1
2
3
4
 final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
						// ......
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
              //看这行,map的remove方法会增加modeCount
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 正确姿势

public class Test {
    public static void main(String[] args) {

        Map<String, String> map = new HashMap<>();
        map.put("123", "123");
        map.put("1231", "123");

        //边遍历,边移除的正确写法
        Set<Map.Entry<String, String>> entries = map.entrySet();
        Iterator<Map.Entry<String, String>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> next = iterator.next();
            if ("123".equals(next.getKey())) {
              //此处调用的是 迭代器 的remove方法
                iterator.remove();
            }
        }

        map.forEach((k, v) -> System.out.println(k + " ---- " + v));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

迭代器的remove为什么就没问题呢?看一下迭代器的remove代码:

        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的值
            expectedModCount = modCount;
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
最后更新时间: 2/2/2023, 2:27:22 AM