在集合类的源码里,像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;
TreeMap部分源码:
/**
* The number of structural modifications to the tree.
*/
private transient int modCount = 0;
AbstractList部分源码:
/**
* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.
*
* <p>This field is used by the iterator and list iterator implementation
* returned by the {@code iterator} and {@code listIterator} methods.
* If the value of this field changes unexpectedly, the iterator (or list
* iterator) will throw a {@code ConcurrentModificationException} in
* response to the {@code next}, {@code remove}, {@code previous},
* {@code set} or {@code add} operations. This provides
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> If a subclass
* wishes to provide fail-fast iterators (and list iterators), then it
* merely has to increment this field in its {@code add(int, E)} and
* {@code remove(int)} methods (and any other methods that it overrides
* that result in structural modifications to the list). A single call to
* {@code add(int, E)} or {@code remove(int)} must add no more than
* one to this field, or the iterators (and list iterators) will throw
* bogus {@code ConcurrentModificationExceptions}. If an implementation
* does not wish to provide fail-fast iterators, this field may be
* ignored.
*/
protected transient int modCount = 0;
无论哪个源码的解释,意思都是modCount记录着结构修改次数,
对于AbstractList而言。modCount字段由iterator和 listIterator方法返回的迭代器和List迭代器实现使用。如果此字段的值意外更改,迭代器(或List迭代器)将抛出ConcurrentModificationException,以响应next方法、 remove、previous、set或 add操作。这提供了快速故障行为,而不是在迭代过程中面对并发修改时的不确定性行为。
拿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是否一致,如果不一致则抛出异常。
这里更详细的说明一下,在迭代器初始化时,赋值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。