关于集合遍历并删除报错详解

list集合删除不要使用增强for,建议使用for(int i;;)这种方法,注意这种方法删除集合元素会导致索引前移导致遍历问题

例如:

private static void delFor() {
        List<String> blist = new ArrayList<>();
        blist.add("a");
        blist.add("b");
        blist.add("c");
        blist.add("d");
        blist.add("e");
        blist.add("f");


        for (int i = 0; i < blist.size(); i++) {
            if(blist.get(i).equals("b")) {
                blist.remove(blist.get(i));
                //这里输出被删除的元素有问题,主要是索引改变,删除本身没问题
                System.out.println("删除的元素是: " +  blist.get(i));
            }

        }
        System.out.println(blist);
    }
复制代码

二、foreach删除

增强for遍历等效于使用迭代器, 在遍历中修改元素数目会报ConcurrentModificationException

private static void delNormal() {
        List<String> alist = new ArrayList<>();
        alist.add("1");
        alist.add("2");
        alist.add("3");

        for (String item : alist) {
            if (item.equals("2")) {  //删除2不报错
                alist.remove(item);
                System.out.println("被删除的元素"+item);
            }

        }
        System.out.println("遍历删除后的集合" + "  " + alist);
    }
    
    结果:被删除的元素2
          遍历删除后的集合  [1, 3]
复制代码

上面结论大家肯定知道,但是看这段代码,删除成功了,并没有报错,结论不对吗?

再看一段代码:

private static void del() {
        List<String> blist = new ArrayList<>();
        blist.add("a");
        blist.add("b");
        blist.add("c");
        blist.add("d");
        blist.add("e");
        blist.add("f");
        for (String item : blist) {
            if(item.equals("b")) {
                blist.remove(item);
                System.out.println("删除的元素是: "+ item );
            }
        }

        System.out.println("删除后的集合为:" +blist);
    }
复制代码

这段代码运行直接报错,是上面描述的异常

private static void delForIterator() {
        List<String> blist = new ArrayList<>();
        blist.add("a");
        blist.add("b");
        blist.add("c");
        blist.add("d");
        blist.add("e");
        blist.add("f");
        Iterator it = blist.iterator();
        while(it.hasNext()) {
            String item = (String) it.next();
            if(item.equals("b")) {
                it.remove();
                System.out.println("删除的元素是: "+ item );
            }

        }

        System.out.println("删除后的集合为:" +blist);
    }
复制代码

正确删除,没问题

我们看下迭代删除的源码:

private class Itr implements Iterator<E> {
        int cursor;       // /将要访问的元素的索引
        int lastRet = -1; // 上一个访问元素的索引
        int expectedModCount = modCount;//expectedModCount为预期修改值,初始化等于modCount(AbstractList类中的一个成员变量)

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            //每次调用next()需要check
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                //重新给expectedModCount值相等
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

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

在获取一个Iterator对象时,会初始化成员变量

  • cursor(0)
  • lastRet(-1)
  • expectedModCount(ModCount=初始集合长度).

对于第一段代码(增强for底层还是调用迭代器),不报错是因为在删除2以后,调用hasNext()方法,cursor值移动至2,size此时变成2,相等,跳出循环,所以没有报错,这仅仅是个巧合而已。

增加for删除报错的主要原因是每次调用next()方法,都会检查expectedModCount和
ModCount值是否相等,当我们删除元素后,ModCount会改变与expectedModCount值不同,引起报错。

使用iterator删除时,看上面源码,会再次赋值它们相等,所以不会报错。

三、多线程情况下的集合删除

使用迭代器的iterator.remove()在单线程下是不会报错的,但是在多线程情况下,一个线程修改了集合的modCount导致另外一个线程迭代时modCount与该迭代器的expectedModCount不相等,这也会报异常。

public class RemoveListForThreads implements Runnable {
    static List<String> alist = new ArrayList<>();

    public static void main(String[] args) {
        RemoveListForThreads s = new RemoveListForThreads();
        alist.add("a");
        alist.add("b");
        alist.add("c");
        alist.add("d");

        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1,
                TimeUnit.SECONDS, workQueue);

        for (int i = 0; i < 5; i++) {
            executor.execute(s);
        }
        executor.shutdown();
    }


    @Override
    public synchronized void run() {
        Iterator<String> iterator = alist.iterator();
            while (iterator.hasNext()) {
                String s = iterator.next();
                if (s.equals("c") && Thread.currentThread().getName().equals("pool-1-thread-1")) {
                    iterator.remove();
                    System.out.println(Thread.currentThread().getName() + " " + s);
                }
                System.out.println(Thread.currentThread().getName() + " " + s);
            }
            System.out.println(alist);
        }
}

复制代码

上面这段代码创建了一个线程池,开启了五个线程,在run方法中遍历并删除“b”元素,如果该方法不加同步sychronized,也会抛出异常

原文 

https://juejin.im/post/5c8779b56fb9a049a571bf0e

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 关于集合遍历并删除报错详解

赞 (0)
分享到:更多 ()

评论 0

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