对Collections的遍历删除方式

摘要:
以集合的子类List为例,List<String>List=newArrayList<>();列表添加(“a”);列表添加(“b”);列表添加(“c”);列表添加(“d”);对于{if{list.remove;}}System.out。打印ln;执行遍历操作。如果列表中的某个元素满足某个条件,它将从list=size();}中删除publicEnext(){checkForComodification();try{Enext=get;lastRet=cursor++;returnnext;}捕获{checkForComodification();thrownewNoSuchElementException();}}Publicvoidremove(){ifthrownewIllegalStateException();checkForComodification());try{AbstractList.this.remove;ifcursor--;lastRet=-1;expectedModCount=modCount;//当迭代器删除时,它将更改其版本}catch{thrownewConcurrentModificationException;}//检查迭代器的版本是否与集合的版本一致,并抛出异常finalvoidcheckForComodification(){if(modCount!=expectedModCount)thrownewConcurrentModificationException();}}需要知道两点:1.modCount。每次列表更改都将使其变为+12。cusor,cursor。当遍历新元素时,在case列中+1,list Size()=4,在遍历集合时创建集合迭代器。此时,expectedModCount=modCount=4,迭代器的游标=0,游标=0。next()检查版本号是否一致,遍历第一个元素cursor=cursor+1=1,打印,然后打印。hasNext()函数判断光标!

以collections的子类List(Arraylist)为例

List<String> list=new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        for (String s:list) {
            if (s.equals("d")) {
                list.remove(s);
            }
        }
        System.out.println(list);

做一个遍历的操作,如果list中的某个元素满足某个条件,则将该元素从list中移除。

经过测试运行,可以发现,除非说要移除的元素位于倒数第二的元素,否则会出现异常,报错如下:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
	at java.util.ArrayList$Itr.next(Unknown Source)
	at Main.main(Main.java:49)

再看如下一例子

List<String> list=new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("c");
        for (String s:list) {
            if (s.equals("c")) {
                list.remove(s);
            }
        }
        System.out.println(list);

期望得到的list应该只包含“a”,“b”两个元素,然而得到的是如下的list:

[a, b, c]

解决办法:

定义一个新的list,将要删除的list放在这个新定义的list中,最后调用removeAll的方法整体删除

List<String> dele=new ArrayList<>();
        List<String> list=new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("c");
        for (String s:list) {
            if (s.equals("c")) {
                dele.add(s);
            }
        }
        list.removeAll(dele);
        System.out.println(list);

错误分析:

首先知道for—each循环是使用迭代器来实现的,找到源码

//    在AbstractList类中  
//    内部成员有:  
//    protected transient int modCount = 0;  
      
//    注:该modCount相当于集合的版本号,集合内容每改变一次,其值就+1。  
//    观察下面内部类可以知道,在next()、remove()方法都会去判断集合版本号与迭代器版本号是否一致checkForComodification();  
//    内部方法有:  
    public Iterator<E> iterator() {  
        return new Itr();  
        }  

    private class Itr implements Iterator<E> {  
        int cursor = 0;  
        int lastRet = -1;  
        //***创建迭代器时就讲版本号给了迭代器  
        int expectedModCount = modCount;  
      
        public boolean hasNext() {  
                return cursor != size();  
        }  
      
        public E next() {  
                checkForComodification();  
            try {  
            E next = get(cursor);  
            lastRet = cursor++;  
            return next;  
            } catch (IndexOutOfBoundsException e) {  
            checkForComodification();  
            throw new NoSuchElementException();  
            }  
        }  
      
        public void remove() {  
            if (lastRet == -1)  
            throw new IllegalStateException();  
                checkForComodification();  
      
            try {  
            AbstractList.this.remove(lastRet);  
            if (lastRet < cursor)  
                cursor--;  
            lastRet = -1;  
            expectedModCount = modCount;//迭代器进行删除时,会改变it的版本  
            } catch (IndexOutOfBoundsException e) {  
            throw new ConcurrentModificationException();  
            }  
        }  
        //检查迭代器的版本与集合的版本是否一致,不同则抛出异常  
        final void checkForComodification() {  
            if (modCount != expectedModCount)  
            throw new ConcurrentModificationException();  
        }  
     }  

需要知道两点:

1.modCount,每一次list改变都会进行使其+1

2.cusor,光标,新遍历元素时候+1

该案列中,list.size()=4,遍历集合时创建集合迭代器,此时有 expectedModCount = modCount=4,迭代器的cursor=0,

1cursor=0,it.next()检查版本号一致,遍历第一个元素,cursor=cursor+1=1,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true

(2cursor=1,it.next()检查版本号一致,遍历第二个元素,cursor=cursor+1=2,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true

(3cursor=2,it.next()检查版本号一致,遍历第三个元素,cursor=cursor+1=3,进入if语句,移除一个元素,此时集合内容改变,版本号modCount++,5.然后it.hasNext()判断cursor(2)!=size(4),true

3cursor=3,it.next()检查发现版本号不一致expectedModCount! = modCount,抛出并发修改异常。

而我们所举的第二个例子可以看出,若要删除的元素位置在倒数第二个中,则遍历会提前结束,原因:

(1cursor=0,it.next()检查版本号一致,遍历第一个元素,cursor=cursor+1=1,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true

(2cursor=1,it.next()检查版本号一致,遍历第二个元素,cursor=cursor+1=2,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true

(3cursor=3,it.next()检查版本号一致,遍历到要删除的元素,cursor=cusor+1=3;进入if语句,list.remove(),此时,版本号modCount++,且集合大小改变list.size()=3,然后,it.hasNext()判断发现cursor(3)==size(3),返回false,迭代提前结束

因此不会爆出异常

免责声明:文章转载自《对Collections的遍历删除方式》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇一句JS搞定只允许输入数字和字母【PowerDesigner】快速上手下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

selenium 难定位元素,时间插件,下拉框定位,string

1.元素定位 ID定位元素: findElement(By.id(“”)); 通过元素的名称定位元素: findElement(By.name(“”)); 通过元素的html中的位置定位元素: findElement(By.xpath(“”)); 通过元素的标签名称定位元素: findElement(By.tagName(“”)); 通过元素的链接名称...

Android学习——移植tr069程序到Android平台

原创作品,转载请注明出处,严禁非法转载。如有错误,请留言! email:40879506@qq.com 声明:本系列涉及的开源程序代码学习和研究,严禁用于商业目的。 如有任何问题,欢迎和我交流。(企鹅号:408797506)  淘宝店:https://shop484606081.taobao.com 本篇用到的代码下载路径:http://download....

死磕 java集合之WeakHashMap源码分析

欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。 简介 WeakHashMap是一种弱引用map,内部的key会存储为弱引用,当jvm gc的时候,如果这些key没有强引用存在的话,会被gc回收掉,下一次当我们操作map的时候会把对应的Entry整个删除掉,基于这种特性,WeakHashMap特别适用于缓存处理。 继承体...

springJPA 之 QueryDSL(一)

引言不可否认的是 JPA 使用是非常方便的,极简化的配置,只需要使用注解,无需任何 xml 的配置文件,语义简单易懂,但是,以上的一切都建立在单表查询的前提下的,我们可以使用 JPA 默认提供的方法,简单加轻松的完成 CRUD 操作。但是如果涉及到多表动态查询, JPA 的功能就显得有些捉襟见肘了,虽然我们可以使用注解 @Query ,在这个注解中写 SQ...

HTML5中实现文件上传下载的三种解决方案(推荐)

前言:因自己负责的项目(jetty内嵌启动的SpringMvc)中需要实现文件上传,而自己对java文件上传这一块未接触过,且对 Http 协议较模糊,故这次采用渐进的方式来学习文件上传的原理与实践。该博客重在实践。 一. Http协议原理简介      HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于19...

org.apache.commons.httpclient工具类

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; import org.ap...