热爱技术,追求卓越
不断求索,精益求精

秒懂java,HashMap出现java.util.ConcurrentModificationException问题

直接看下面的代码

@Test
public void test4(){
    try {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        Set<Integer> set = map.keySet();
        for(Integer k : set){
            map.remove(k);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

允许测试代码出现错误java.util.ConcurrentModificationException:

java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
    at java.util.HashMap$KeyIterator.next(HashMap.java:828)
    at cn.lovecto.test.SimpleTest.test4(SimpleTest.java:77)

我们来看报错的地方,HashMap.java中第828行:

private final class KeyIterator extends HashIterator<K> {
    public K next() {
        return nextEntry().getKey();
    }
}

再看报错行HashMap.java中的793,报错原因是modCount不等于expectedModCount:

final Entry<K,V> nextEntry() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    Entry<K,V> e = next;
    if (e == null)
        throw new NoSuchElementException();

    if ((next = e.next) == null) {
        Entry[] t = table;
        while (index < t.length && (next = t[index++]) == null)
            ;
    }
    current = e;
    return e;
}

我们再来看modCount的定义:

/**
 * 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 volatile int modCount;

modCount是HashMap的一个成员变量,该值表示对HashMap的修改次数,查看HashMap的put()和remove()方法就可以发现,每次调用put()方法或者remove()方法就会对modCount进行加1操作,下面是put操作:

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

expectedModCount是HashMap内部类HashIterator的成员变量,expectedModCount的值在构造的时候初始化为modCount,以后只有在remove的时候改变,构造代码片段:

HashIterator() {
    expectedModCount = modCount;
    if (size > 0) { // advance to first entry
        Entry[] t = table;
        while (index < t.length && (next = t[index++]) == null)
            ;
    }
}

remove代码片段:

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;
}

OK,找到了modCount和expectedModCount之间的关系,那么解决办法就有了。当使用HashMap的keySet方法获取key集合后,使用迭代器Iterator的方式remove:

@Test
public void test7(){
    try {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        Set<Integer> set = map.keySet();
        Iterator<Integer> it = set.iterator();
        while (it.hasNext()) {
            Integer v = it.next();
            it.remove();
            System.out.println(map);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

同理,除了Map外,类似List,Set等集合类,遍历的时候如果有修改操作,尽量使用Iterator:

//错误的
@Test
public void test5(){
    try {
        List<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        for(Integer k : list){
            list.remove(k);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

//正确的
@Test
public void test6(){
    try {
        List<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        Iterator<Integer> it = list.iterator();
        while (it.hasNext()) {
            it.next();
            it.remove();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

java.util.ConcurrentModificationException问题通常是由于遍历的时候对集合或map发生了修改导致,使用Iterator对集合或map进行比那里操作是更好的选择。

赞(5)
未经允许不得转载:LoveCTO » 秒懂java,HashMap出现java.util.ConcurrentModificationException问题

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

热爱技术 追求卓越 精益求精