集合遍历删除元素异常ConcurrentModificationException(并发修改异常)

集合遍历过程中删除元素报错ConcurrentModificationException(并发修改异常)

今天敲代码时遇到一个异常,代码如下:

List<Integer> list = new ArrayList<>();
list:[50, 42, 56, 76, 62, 38, 42, 98, 66, 91, 53, 56, 97, 61, 33, 26, 97, 45]        
//遍历list去除奇数
Iterator<Integer> iterator = list.iterator();//获取ArrayList的迭代器
    //迭代器遍历
while(iterator.hasNext()){
    Integer tempNum = iterator.next();
    if (tempNum % 2 != 0){
         list.remove(tempNum);//报错ConcurrentModificationException(并发修改异常)
    }
 }

查询资料后发现ConcurrentModificationException是Java集合的一个快速失败(fail-fast)机制,防止多个线程同时修改同一个集合的元素。

直接使用list对象调用remove方法list.remove(),改变了modCount的值,但是没有改变expectedModCount的值,如果此时继续调用iterator的方法,就会出现ConcurrentModificationException(并发修改异常)。

有关错误判断机制请继续往下看:

当获取到ArrayList的迭代器iterator时会返回一个Itr对象,通过它里面的remove来删除元素

iterator.remove()->该方法会自动修改expectedModCount的值,保证其与modCount一致,这样就不会报错了.

ArrayList部分源码:

public Iterator<E> iterator() {
        return new Itr();
    }

ArrayList中的私有类**Itr**类部分源码:

 private class Itr implements Iterator<E> {
     	//游标。指向下一个返回的元素
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
     	//修改次数,用于检测在迭代器遍历过程中集合是否发生改变
     	//modCount -> 当前状态下的集合修改次数
     	//ecpectedModCount -> 遍历过程中的集合修改次数
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }
     
        ......
            
		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();
            }
        }

		......
            
		//错误检测方法,通过比较modCount与expectedModCount是否一致,不一致则抛出异常
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

了解到这些之后,正确代码如下:

List<Integer> list = new ArrayList<>();
list:[50, 42, 56, 76, 62, 38, 42, 98, 66, 91, 53, 56, 97, 61, 33, 26, 97, 45]        
//遍历list去除奇数
Iterator<Integer> iterator = list.iterator();//获取ArrayList的迭代器
    //迭代器遍历
while(iterator.hasNext()){
    Integer tempNum = iterator.next();
    if (tempNum % 2 != 0){
         iterator.remove();//代码不会报错了
    }
 }

注意:

  1. ArrayList可以使用普通for循环来遍历,效率可以,但是LinkedList不建议使用普通for循环遍历,数据多的话效率太差
  2. 对于Map实现类HashMapSet的实现类HashSet也是一样的,在迭代器遍历过程中也是不能修改的,另一种遍历方式:增强for循环其底层也是通过迭代器来遍历的。
  3. 这里提供一种解决方案,通过一个新的集合来存储你需要的数据,最后输出这个新的集合内容。

附加:

集合主要用到三种 :

img

Collection接口中的一些方法:

boolean add(E e);	//添加单个元素
boolean addAll(Collection<? extends E> c);	//添加一个类型为E或者其子类类型的集合
boolean remove(Object o);	//删除一个元素
boolean removeAll(Collection<?> c);	//删除一个集合
int size();	//返回集合大小
boolean contains(Object o);	//判断集合当中是否存在 o
boolean containsAll(Collection<?> c);	//判断一个集合是否在该集合当中
void clear();	//清空集合
boolean equals(Object o);	//比较是否相等
boolean isEmpty();	//判断集合是否为空

Set接口:

Set接口下有一个 HashSet 实现类,HashSet的底层是用 HashMap 实现的,因此,查询效率高。由于采用Hashcode算法直接确定元素的内存地址,增删效率也高。
HashSet 接口中的元素 无序不可重复 ,不包含重复元素,最多包含一个 null,元素没
有顺序 。

创建一个Set集合:

Set<E> set = new HashSet<>();//这个E代表可以存储任意类型的数据-》泛型

Set集合遍历方式:

public class SetTest {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("aaa");
        set.add("bbb");
        set.add("ccc");
        set.add("ddd");

        //增强for循环
        for (String s : set) {
            System.out.println(s);
        }
        System.out.println("-----------------------");
        //迭代器循环
        Iterator<String> iterator = set.iterator();	//获取迭代器
        while(iterator.hasNext()){	//判断是否还有下一个元素
            String tempString = iterator.next();	//指向下一个元素
            System.out.println(tempString);
        }
    }
}

        //输出结果,与放进去的顺序不符,说明了set集合的无序存储
        aaa
        ccc
        bbb
        ddd
        -----------------------
        aaa
        ccc
        bbb
        ddd

List接口:

List接口下有两个实现类,LinkedListArrayList都实现了List的方法,

并且都给出了可以存储有序、重复数据的空间。

LinkedList:

底层用双向链表实现的List。特点:查询效率低,增删效率高,线程不安全。

ArrayList:

底层用数组实现的List。特点:查询效率高,增删效率低,线程不安全。

List集合的创建:

ArrayList<E> list = new ArrayList<>();//这个E代表可以存储任意类型的数据-》泛型

list遍历:

public class ArrayListTest {
    public static void main(String[] args) {

        ArrayList list = new ArrayList();
        list.add("y");
        list.add("l");
        list.add("s");
        list.add("l");
        System.out.println(list);
		//普通for循环遍历
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        System.out.println("-----");
		//增强for循环遍历
        for (Object o : list) {
            System.out.println(o);
        }
        System.out.println("-----");
		//迭代器遍历
        Iterator iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}
//输出结果,顺序输出,和添加进去的顺序一致
[y, l, s, l]
y
l
s
l
-----
y
l
s
l
-----
y
l
s
l

Map接口:

Map只是一个接口,其中提供了Map的规范,即各种方法的定义

Object put(Object key, Object value);//添加元素
Object get(Object key);//根据key值获取value值
Object remove(Object key);//根据key值移除value值
boolean containsKey(Object key);//根据key值判断map中是否含有该key值,返回true or false
boolean containsValue(Object value);//根据value值判断map中是否含有该value值,同上
int size();//获取map的大小
boolean isEmpty();//判断map是否为空
void putAll(Map t);//将一个map直接添加到另一个map中
void clear();//清空所有元素

常见的实现类有 HashMap 、 HashTable 等

HashMap中存储数据的特点:

  1. 存储的元素是以K-V的形式存在的
  2. map集合中 key 必须要唯一,如果添加了相同的键值对(键相同)会发生覆盖
  3. map集合中元素(键值对)是无序的,和Set集合类似

注意: 如果存入的键值对中key相同,那么后面的会覆盖掉前面的数据。

当数据存放到Map之后,我们是无法控制存放数据的顺序的

创建一个Map集合:

Map<K, V> map = new HashMap<>();//这里K,V代表键值对的类型,都是泛型,想存什么类型就存什么类型

map遍历:

public class DemoHashMap {
    public static void main(String[] args) {
        
        Map<Integer,String> num = new HashMap<>();
        num.put(1, "一,Ⅰ,壹");
        num.put(2, "二,Ⅱ,贰");
        num.put(3, "三,Ⅲ,叁");
        num.put(4, "三,Ⅲ,叁");

        System.out.println("-----------keySet-----------");
        Set<Integer> keys = num.keySet();
        System.out.println("---------增强for------");
            for(Integer key : keys){
            System.out.println(key + "-" + num.get(key));
        }
        System.out.println("-------迭代器-------");
        Iterator<Integer> keyIt = keys.iterator();
        while(keyIt.hasNext()){
            // 获取下一个key
            Integer key = keyIt.next();
            String value = num.get(key);
            System.out.println(key + "-" + value);
        }

        System.out.println("===========Entry的遍历==========");
        Set<Entry<Integer, String>> entrySet= num.entrySet();
        for(Entry<Integer, String> entry : entrySet){
            Integer key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "-" + value);
        }

        System.out.println("-------迭代器-----------");

        Iterator<Entry<Integer,String>> it =  entrySet.iterator();
        while(it.hasNext()){
            Entry<Integer, String> en = it.next();
            System.out.println(en.getKey() + "-" + en.getValue());
        }

        System.out.println("------------遍历所有value--------------");
        Collection<String> values = num.values();
        for(String v :values){
            System.out.println(v);
        }
        System.out.println("--------");
        Iterator<String> iterator = values.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }
}

//输出结果
-----------keySet-----------
---------增强for------
1-一,Ⅰ,壹
2-二,Ⅱ,贰
3-三,Ⅲ,叁
4-三,Ⅲ,叁
-------迭代器-------
1-一,Ⅰ,壹
2-二,Ⅱ,贰
3-三,Ⅲ,叁
4-三,Ⅲ,叁
===========Entry的遍历==========
1-一,Ⅰ,壹
2-二,Ⅱ,贰
3-三,Ⅲ,叁
4-三,Ⅲ,叁
-------迭代器-----------
1-一,Ⅰ,壹
2-二,Ⅱ,贰
3-三,Ⅲ,叁
4-三,Ⅲ,叁
------------遍历所有value--------------
一,Ⅰ,壹
二,Ⅱ,贰
三,Ⅲ,叁
三,Ⅲ,叁
--------
一,Ⅰ,壹
二,Ⅱ,贰
三,Ⅲ,叁
三,Ⅲ,叁

参考链接:

https://blog.csdn.net/shengongbao114/article/details/85006658

https://www.cnblogs.com/xiaofengcanyuelong/p/13302096.html

https://blog.csdn.net/weixin_44745208/article/details/104390703

https://www.cnblogs.com/Berryxiong/p/6134012.html

第一次写博客,如果有什么出错的地方欢迎大家指正,如果引用其他人的博客有什么不当之处,欢迎博客作者来信,立刻改正。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值