zoukankan      html  css  js  c++  java
  • ArrayList在for循环中remove所产生的问题

    背景:

    刚入职公司的时候,就听到面试官在面试过程中提问ArrayList在for循环中remove的问题,当时很庆幸自己没被问到,一年后又一次听到面试在问这个问题。发现自己还没有深入研究一下,所以就有了今天这篇文章。

    代码如下:

    import java.util.ArrayList;
    import java.util.List;
    
    public class ArrayListTest {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");
            list.add("ddd");
    
            for (String str : list) {
                if ("aaa".equals(str)) {
                    list.remove("aaa");
                }
            }
        }
    }
    

    以上代码的执行会导致以下异常:

    Exception in thread "main" java.util.ConcurrentModificationException
    	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    	at java.util.ArrayList$Itr.next(ArrayList.java:859)
    	at future.ArrayListTest.main(ArrayListTest.java:14)
    

    今天通过一种通俗易懂的方式说明代码异常的原因!

    1️⃣list.add

    通过查看 list.add 方法,发现有一个变量的值发生了改变,这个变量叫 modCount 。在上面demo中一共执行了四次 add 操作,所以 modCount 的值为4.

    注:list 的 add()、remove() 和 clear() 都会改变 modCount 值。

    image-20210421143503040

    2️⃣for(String str : list)

    for(String str : list) 调用的是 ArrayList 的内部类 Itr ,Itr是对 Iterator 的实现。在 Iterator 开始之前,会先执行 modCount != expectedModCount 。

    此时 modCount 和 expectedModCount 的值均为 4 .

    3️⃣list.remove("aaa")

    先看一下报错原因,这里是源码:

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
    

    即 modcount 和 expectedModCount 的值不相等了。看到这里应该有人会想,如果每次执行 remove 后,将 expectedModCount = modcount 不就好了。其实是有的,只是我们调用的方法错了。

    list.remove("aaa")调用的 remove 源码如下

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    //走的是这个remove
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    

    而使 modCount 的值改变的是其中的 fastRemove 方法。

    fastRemove 源码如下:

    /*
         * Private remove method that skips bounds checking and does not
         * return the value removed.
         */
    private void fastRemove(int index) {
        //此处 modCount + 1
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
    

    通过查看上面的源码发现 list.remove(E e) 方法执行完成后只执行了 modcount++,并没有将值赋给 expectedModCount。

    而真正的 expectedModCount = modCount 执行的源码如下:

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
    
        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    

    该方法位于内部类 Itr 中。

    这也就是说为什么如果 list 在循环中有删除操作,最好用 Iterator 的方式来做。

    结论

    简单总结一下

    • list.remove() 没有对 expectedModCount 重新赋值
    • iterator.remove() 每次 remove 之后会对 expectedModCount 重新赋值
    If you’re going to reuse code, you need to understand that code!
  • 相关阅读:
    2013面试C++小结
    Linux C 面试题总结 .
    [SCOI2011]糖果
    python——简单爬虫
    python——ADSL拨号程序
    python——处理xls表格
    Vsphere初试——架设Panabit行为管理
    Vsphere初试——使用Vsphere client
    Vsphere初试——基本安装
    Python2与Python3的不同点
  • 原文地址:https://www.cnblogs.com/leizzige/p/14685275.html
Copyright © 2011-2022 走看看