我们能够发现,在集合类的源码里,像HashMap、TreeMap、ArrayList、LinkedList等都有modCount属性,字面意思就是修改次数,首先看一下源码里对此属性的注释
HashMap部分源码:
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
注释汉译:
此哈希表已被结构修改的次数,结构修改是指那些更改哈希表或以其他方式修改其内部结构(例如,重新哈希)。 此字段用于HashMap集合迭代器的快速失败。
所以,modCount主要是为了防止在迭代过程中某些原因改变了原集合,导致出现不可预料的情况,从而抛出并发修改异常,这可能也与Fail-Fast机制有关。在可能出现错误的情况下提前抛出异常终止操作。
然后来看一下HashMap的put方法源码(部分截取):
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
HashMap的remove方法源码(部分截取):
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;
++modCount;
--size;
afterNodeRemoval(node);
return node;
通过比较put方法和remove方法可以看出,对于已经存在的key进行put修改value的时候,对modCount没有修改,remove方法则进行了modCount自增操作,所以只有当对HashMap元素个数产生影响的时候才会修改modCount。
那么修改modCount有什么用呢?
这里用HashMap举例,大家知道当用迭代器遍历HashMap的时候,调用HashMap.remove方法会产生ConcurrentModificationException异常,这是因为remove改变了HashMap集合的元素个数,导致集合的结构发生变化。
示例:
public static void main(String args[]) {
Map<String, String> map = new HashMap<>();
map.put("1", "zhangsan");
map.put("2", "lisi");
map.put("3", "wangwu");
Iterator<String> iterator = map.keySet().iterator();
while(iterator.hasNext()) {
String name = iterator.next();
map.remove("1");
}
}
执行结果:
抛出ConcurrentModificationException异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
at java.util.HashMap$KeyIterator.next(HashMap.java:1466)
at com.cesec.springboot.system.service.Test.main(Test.java:14)
我们看一下抛出异常的KeyIterator.next()方法源码:
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
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;
}
在迭代器初始化时,会赋值expectedModCount,在迭代过程中判断modCount和expectedModCount是否一致,如果不一致则抛出异常,可以看到KeyIterator.next()调用了nextNode()方法,nextNode()方法中进行了modCount与expectedModCount判断。
这里更详细的说明一下,在迭代器初始化时,赋值expectedModCount,假设与modCount相等,都为0,在迭代器遍历HashMap每次调用next方法时都会判断modCount和expectedModCount是否相等,当进行remove操作时,modCount自增变为1,而expectedModCount仍然为0,再调用next方法时就会抛出异常。
所以迭代器遍历时如果想删除元素需要通过迭代器的删除方法进行删除
那么为什么通过迭代器删除就可以呢?
HashIterator的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 = modCount;
}
通过迭代器进行remove操作时,会重新赋值expectedModCount。