iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >java集合类遍历的同时如何进行删除操作
  • 342
分享到

java集合类遍历的同时如何进行删除操作

2024-04-02 19:04:59 342人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录java集合类遍历的同时进行删除操作1. 背景2. 代码示例3. 分析java集合中的一个移除数据陷阱遍历集合自身并同时删除被遍历数据异常本质原因解决java集合类遍历的同时进行

java集合类遍历的同时进行删除操作

1. 背景

在使用java的集合类遍历数据的时候,在某些情况下可能需要对某些数据进行删除。往往操作不当,便会抛出一个ConcurrentModificationException,本方简单说明一下错误的示例,以及一些正确的操作并简单的分析下原因。

P.S. 示例代码和分析是针对List的实例类ArrayList,其它集合类可以作个参考。

2. 代码示例

示例代码如下,可以根据注释来说明哪种操作是正确的:


public class TestIterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        print(list);
 
        // 操作1:错误示范,不触发ConcurrentModificationException
        System.out.println("NO.1");
        List<String> list1 = new ArrayList<>(list);
        for(String str:list1) {
            if ("4".equals(str)) {
                list1.remove(str);
            }
        }
        print(list1);
        // 操作2:错误示范,使用for each触发ConcurrentModificationException
        System.out.println("NO.2");
        try{
            List<String> list2 = new ArrayList<>(list);
            for(String str:list2) {
                if ("2".equals(str)) {
                    list2.remove(str);
                }
            }
            print(list1);
        }catch (Exception e) {
            e.printStackTrace();
        }
        // 操作3:错误示范,使用iterator触发ConcurrentModificationException
        try{
            System.out.println("NO.3");
            List<String> list3 = new ArrayList<>();
            Iterator<String> iterator3 = list3.iterator();
            while (iterator3.hasNext()) {
                String str = iterator3.next();
                if ("2".equals(str)) {
                    list3.remove(str);
                }
            }
            print(list3);
        }catch (Exception e){
            e.printStackTrace();
        }
        // 操作4: 正确操作
        System.out.println("NO.4");
        List<String> list4 = new ArrayList<>(list);
        for(int i = 0; i < list4.size(); i++) {
            if ("2".equals(list4.get(i))) {
                list4.remove(i);
                i--; // 应当有此操作
            }
        }
        print(list4);
        // 操作5: 正确操作
        System.out.println("NO.5");
        List<String> list5 = new ArrayList<>(list);
        Iterator<String> iterator = list5.iterator();
        while (iterator.hasNext()) {
            String str = iterator.next();
            if ("2".equals(str)) {
                iterator.remove();
            }
        }
        print(list5);
 
    }
 
    public static void print(List<String> list) {
        for (String str : list) {
            System.out.println(str);
        }
    }
}

P.S. 上面的示例代码中,操作1、2、3都是不正确的操作,在遍历的同时进行删除,操作4、5能达到预期效果,推建使用第5种写法。

3. 分析

首先,需要先声明3个东东:

  • 1. for each底层采用的也是迭代器的方式(这个我并没有验证,是查找相关资料得知的),所以对for each的操作,我们只需要关注迭代器方式的实现即可。
  • 2. AraayList底层是采用数组进行存储的,所以操作4实现是不同于其它(1、2、3、5)操作的,他们用的都是迭代器方式。
  • 3. 鉴于1、2点,其实本文重点关注的是采用迭代器的remove(操作5)为什么没有问题,而采用集合的remove(操作1、2、3)就不行。

3.1 为什么操作4没有问题


        // 操作4: 正确操作
        System.out.println("NO.4");
        List<String> list4 = new ArrayList<>(list);
        for(int i = 0; i < list4.size(); i++) {
            if ("2".equals(list4.get(i))) {
                list4.remove(i);
                i--; // 应当有此操作
            }
        }

这个其实没什么太多说的,ArrayList底层采用数组,它删除某个位置的数据实际上就是把从这个位置下一位开始到最后位置的数据在数组里整体前移一位(基本知识,不多说明)。所以在遍历的时候,重点其实是索引值的大小,底层实现是需要依赖这个索引 的,这也是为什么最后有个i--,因为我们删除2的时候,索引值i为1,删除的时候,就把索引为2到list.size()-1的数据都前移一位,如果不把i-1,那么下一轮循环时,i的值就为2,这样就把原来索引值为2,而现在索引值为1的数据给漏掉了,这个地方需要注意一下。比如,如果原数据中索引1、2的数据都为2,想把2都删除掉,如果不进行i--,那么把索引1处的2删除掉后,下一次循环判断时,就会把原来索引为2,现在索引为1的这个2给遗漏掉了。

3.2 采用迭代时ConcurrentModificationException产生的原因

其实这个异常是在迭代器的next()方法体调用checkForComodification()时抛出来的:

看下迭代器的这两个方法的源码


       public E next() {
            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];
        }
        final void checkForComodification() {
            // 问题就在modCount与expectedModCount的值
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

1. 首先说明,modCount这个值是ArrayList的一个变量,而expectedModCount是迭代器的一个变量。

modCount:该值是在集合的结构发生改变时(如增加、删除等)进行一个自增操作,其实在ArrayList中,只有删除元素时这个值才发生改变。

expectedModCount:该值在调用集合的iterator()方法实例化迭代器的时候,会将modCount的值赋值给迭代器的变量 expectedModCount。也就是说,在该迭代器的迭代操作期间,expectedModCount的值在初始化之后便不会进行改变,而modCount的值却可能改变(比如进行了删除操作),这也是每次调用next()方法的时候,为什么要比较下这两个值是否一致。

其实,我是把它们看作类似于CAS理论的实现来理解的,其实在迭代器遍历的时候调用集合的remove方法,代码上看起来是串行的,但是可以认为是两个不同线程的并行操作这个ArrayList对象(我也是看了下其它资料,才试着这样去理解)。

3.3 为什么在遍历时使用迭代器的remove没有问题

依据3.2条,我们知道,既然使用ArrayList的remove方法出现ConcurrentModificationException的原因在于modCount与expectedCount的值,那么问题就很明晰了,先看下迭代器的remove方法的源码:


        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            // 虽然这里也调用了这方法,但是本次我们可以先忽略,因为这个remove()方法是
            //iterator自已的,也就是可以看作遍历和删除是串行发生的,目前我们尚未开始进行移除
            //操作,所以这里的校验不应该抛出异常,如果抛出了ConcurrentModificationException,
            //那只能是其它线程改了当前集合的结构导致的,并不是因为我们本次尚未开始的移除操作
            checkForComodification();
 
            try {
                // 这里开始进行移除
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                // 重新赋值,使用expectedModCount与modCount的值保持一致
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
     }

注意看下我的注释,在调用迭代器的remove方法时,虽然也是在调用集合的remove方法 ,但是因为这里保持了modCount与expectedModCount的数据一致性,所以在下次调用next()方法,调用checkForComodification方法时,也就不会抛出ConcurrentModificationException了。

3.4 为什么操作1没有抛出ConcurrentModificationException

其实操作1虽然使用for each但是上面说过,其实底层依然是迭代器的方式,这既然是迭代器,然而采用集合的remove方法,却没有抛出ConcurrentModificationException, 这是因为移除的元素是倒数第二个元素的原因。

迭代器迭代的时候,调用hasNext()方法来判断是否结束迭代,若没有结束,才开始调用next()方法,获取下一个元素,在调用next()方法的时候,因为调用 checkForComodification方法时抛出了ConcurrentModificationException。

所以,如果在调用hasNext()方法之后结束循环,不调用next()方法,就不会发生后面的一系列操作了。

既然还有最后一个元素,为什么会结束循环,问题就在于hasNext()方法,看下源码:


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

其实每次调用next()方法迭代的时候,cursor都会加1,cursor就相当于一个游标,当它不等于集合大小size的时候,就会一直循环下去,但是因为操作1移除了一个元素,导致集合的size减一,导致在调用hasNext()方法,结束了循环,不会遍历最后一个元素,也就不会有后面的问题了。

java集合中的一个移除数据陷阱

遍历集合自身并同时删除被遍历数据

使用Set集合时:遍历集合自身并同时删除被遍历到的数据发生异常


Iterator<String> it = atomSet.iterator(); 
  while (it.hasNext()) {  
   if (!vars.contains(it.next())) {
    atomSet.remove(it.next());
   }
  } 

抛出异常:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
at test2.Test1.main(Test1.java:16)

异常本质原因

Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationEx ception 异常。

所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

解决

使用Iterator的remove方法


Iterator<String> it = atomVars.iterator();  
   while (it.hasNext()) {  
    if (!vars.contains(it.next())) {
     it.remove();
    }
   } 

代码能够正常执行。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

--结束END--

本文标题: java集合类遍历的同时如何进行删除操作

本文链接: https://www.lsjlt.com/news/135591.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • java集合类遍历的同时如何进行删除操作
    目录java集合类遍历的同时进行删除操作1. 背景2. 代码示例3. 分析java集合中的一个移除数据陷阱遍历集合自身并同时删除被遍历数据异常本质原因解决java集合类遍历的同时进行...
    99+
    2024-04-02
  • Java map为什么不能遍历的同时进行增删操作
    目录foreach循环?HashMap遍历集合并对集合元素进行remove、put、add1、现象2、细究底层原理前段时间,同事在代码中KW扫描的时候出现这样一条: 上面出现这样的...
    99+
    2024-04-02
  • Java map不能遍历同时进行增删操作的原因是什么
    这篇文章主要介绍“Java map不能遍历同时进行增删操作的原因是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java map不能遍历同时进行增删操作的原因是什么”文章能帮...
    99+
    2023-07-02
  • thinkphp5如何进行删除操作
    本篇内容介绍了“thinkphp5如何进行删除操作”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!首先,我们需要了解ThinkPHP5的基本目...
    99+
    2023-07-05
  • java中的集合元素怎么利用foreach进行遍历
    本篇文章给大家分享的是有关java中的集合元素怎么利用foreach进行遍历,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。代码示例import java.util.*; pu...
    99+
    2023-05-31
    java foreach 遍历
  • java关于list集合做删除操作时的坑及解决
    目录关于list集合做删除操作时的坑解决办法对List集合的常用操作1.list中添加,获取,删除元素2.list中是否包含某个元素3.list中根据索引将元素数值改变(替换)4.l...
    99+
    2024-04-02
  • java中怎么解决list集合做删除操作时的坑
    本篇内容主要讲解“java中怎么解决list集合做删除操作时的坑”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java中怎么解决list集合做删除操作时的坑”吧!关于list集合做删除操作时的坑...
    99+
    2023-06-25
  • MongoDB中如何实现创建、删除集合操作
    这篇文章给大家分享的是有关MongoDB中如何实现创建、删除集合操作的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。创建集合语法格式db.createCollection(name...
    99+
    2024-04-02
  • 进阶JAVA篇- Collection 类的常用的API与 Collection 集合的遍历方式
    目录         1.0 Collection 类的说明         1.1 Collection 类中的实例方法         2.0 Collection 集合的遍历方式(重点)         2.1 使用迭代器( Iter...
    99+
    2023-10-21
    java 开发语言
  • Java JUC中如何操作List安全类的集合
    小编给大家分享一下Java JUC中如何操作List安全类的集合,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!不安全的集合在单线程应用中,通常采取new ArrayList(),指定一个List集合,用于存放可重复的数据。...
    99+
    2023-06-20
  • php如何操作数据库进行模糊删除
    本篇内容主要讲解“php如何操作数据库进行模糊删除”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“php如何操作数据库进行模糊删除”吧!连接数据库在进行数据库操作之前,需要先连接数据库。可以使用 ...
    99+
    2023-07-05
  • Ajax如何遍历jSon后对每一条数据进行相应的修改和删除
    这篇文章主要介绍Ajax如何遍历jSon后对每一条数据进行相应的修改和删除,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!具体代码如下所示:$.ajax({ url: &qu...
    99+
    2024-04-02
  • 微信小程序中如何进行删除处理操作
    这篇文章主要介绍微信小程序中如何进行删除处理操作,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!没有 confrim 那怎么实现这个效果呢?可以使用小程序里的模态框 代码:wxml:&...
    99+
    2024-04-02
  • MySQL中如何用WHILE循环进行批量删除操作
    在MySQL中,没有类似于编程语言中的WHILE循环结构。但是可以利用循环语句和游标来达到类似的效果。以下是一个示例,演示如何使用游...
    99+
    2024-04-30
    MySQL
  • win10如何对多个文件进行右键同时操作
    本篇内容主要讲解“win10如何对多个文件进行右键同时操作”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“win10如何对多个文件进行右键同时操作”吧!直接打开win10系统搜索框,输入reged...
    99+
    2023-06-27
  • PHP7中的迭代器:如何更高效地遍历和操作数据集?
    PHP7中的迭代器:如何更高效地遍历和操作数据集?在PHP7中,迭代器是一个非常强大的概念,它可以让我们更高效地遍历和操作数据集。迭代器可以简化代码结构,提高代码的可读性和可维护性。本文将介绍PHP7中的迭代器概念,并提供一些具体的代码示例...
    99+
    2023-10-22
    迭代器 操作 遍历
  • Lunarpages主机如何对数据库进行创建和删除操作
    Lunarpages主机如何对数据库进行创建和删除操作,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。进入LPCP面板,在面板左侧侧边栏点击 Mysql Mana...
    99+
    2023-06-12
  • PHP7中的迭代器:如何高效地遍历和操作大规模数据集?
    PHP7中的迭代器:如何高效地遍历和操作大规模数据集?引言:随着互联网和数据科学的快速发展,处理大规模数据集成为了很多开发者和数据分析师的常见需求。PHP7中的迭代器是一种强大的工具,可以帮助我们高效地遍历和操作大规模数据集。本文将介绍PH...
    99+
    2023-10-22
    迭代器 大规模数据集 高效遍历
  • 在SQLite中如何执行删除重复记录的操作
    在SQLite中,可以通过以下步骤执行删除重复记录的操作: 首先,使用SELECT语句找出所有重复记录。可以使用GROUP...
    99+
    2024-04-02
  • XamarinAndroid中在RecylerView子元素进行添加/删除操作时如何实现透明动画效果
    这篇文章主要为大家展示了“XamarinAndroid中在RecylerView子元素进行添加/删除操作时如何实现透明动画效果”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“XamarinAndr...
    99+
    2023-06-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作