源码剖析-List遍历删除异常

情景

遍历List,且删除元素

  • 普通for循环遍历删除

  • 增强for循环遍历删除

  • Lambda forEach循环遍历删除

  • Lambda filter过滤

  • 迭代器遍历删除

结果

  • 普通for循环遍历删除,异常中断执行

  • 增强for循环遍历删除,异常中断执行

  • Lambda forEach循环遍历删除,异常中断执行

  • Lambda filter过滤,正常执行

  • 迭代器遍历删除,正常执行

异常

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at com.topsec.topsa.TopsaSearchApplication.main(TestApplication.java:77)

异常原因

  • list遍历下一个元素的时候,发现list本身的modCount != list内部迭代器的expectedModCount,抛出异常ConcurrentModificationException。

  • 普通for循环删除元素,调用list本身的remove()方法,修改了modCount。

  • 迭代器循环删除元素,调用迭代器的remove方法,方法里再通过调用list本身的remove()方法进行元素删除,修改了modCount,同时修改了迭代器的expectedModCount。

  • 增强for循环删除元素,底层循环遍历数据是用的迭代器,删除数据用list本身的remove()方法,修改了modCount,但是无法修改迭代器的expectedModCount,因此遍历下一个元素的时候modCount!= expectedModCount,就会抛异常。

    //代码
    for (Integer integer : list) {
        list.remove(integer);
    }
    
    //反编译后
    Iterator var4 = list.iterator();
    while(var4.hasNext()) {
        Integer integer = (Integer)var4.next();
        list.remove(integer);
    }
    

源码分析

ArrayList(remove)

/*删除此列表中指定位置的元素。将任何后续元素向左移动(从其索引中减去一)。
参数:index–要删除的元素的索引
返回值:从列表中删除的元素
抛出异常:IndexOutOfBoundsException–如果索引超出范围(index<0|| index>=size())
*/
public E remove(int index) {
    //检查给定的索引是否在范围内
    rangeCheck(index);

    //此列表在结构上被修改的次数加1
    modCount++;
    //获取要删除的元素值
    E oldValue = elementData(index);
	//计算要移动元素的个数 list长度-要删除元素的下表-1
    int numMoved = size - index - 1;
    if (numMoved > 0)
        //进行数组赋值,将数组中要删除元素后的元素整体向左移动,覆盖要删除的元素
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //将数组末尾的元素置为null,同时便于jvm进行垃圾回收
    elementData[--size] = null; // clear to let GC do its work
	//返回已删除的元素值
    return oldValue;
}

/*
从该列表中删除指定元素的第一个出现(如果存在)。如果列表中不包含该元素,则该元素将保持不变。更正式地说,删除索引i最低的元素,使得(o==null?get(i)==null:o.equals(get(i)))(如果存在这样的元素)。如果此列表包含指定的元素(或者等效地,如果此列表因调用而更改),则返回true。
参数:o–要从此列表中删除的元素(如果存在)
返回值:如果此列表包含指定的元素,则为true
*/
public boolean remove(Object o) {
    if (o == null) {
       	//当参数等于null时,遍历数组,找到值等于null的元素,将之删除
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        //当参数不等于null时,遍历数组,找到值等于给定参数值的元素,将之删除
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

/*
跳过边界检查而不执行的私有remove方法
并且不返回被删除的值
*/
private void fastRemove(int index) {
    //此列表在结构上被修改的次数加1
    modCount++;
    //计算要移动元素的个数 list长度-要删除元素的下表-1
    int numMoved = size - index - 1;
    if (numMoved > 0)
        //进行数组赋值,将数组中要删除元素后的元素整体向左移动,覆盖要删除的元素
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //将数组末尾的元素置为null,同时便于jvm进行垃圾回收
    elementData[--size] = null; // clear to let GC do its work
}

/**
检查给定的索引是否在范围内。如果没有,则抛出适当的运行时异常。此方法不*检查索引是否为负数:它总是在数组访问之前使用,如果索引为负数,则会抛出ArrayIndexOutOfBoundsException。
*/
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

ArrayList内部类迭代器(Itr)

/**
AbstractList.Itr的一个版本
*/
private class Itr implements Iterator<E> {
    //访问列表下一个元素的下标,默认等于0
    int cursor;       // index of next element to return
    //遍历访问的最后一个元素的下标,及迭代遍历当前元素的下标
    int lastRet = -1; // index of last element returned; -1 if no such
    //此列表在结构上预期被修改的次数
    int expectedModCount = modCount;

    Itr() {}

    //是否还有下一个元素
    public boolean hasNext() {
        return cursor != size;
    }

    //获取列表下一个元素
    @SuppressWarnings("unchecked")
    public E next() {
        //列表预期修改次数与实际修改次数检查
        checkForComodification();
        //将访问列表下一个元素的下标赋值给i
        int i = cursor;
        //如果下一个元素的下表大于等于列表的长度,则抛异常
        if (i >= size)
            throw new NoSuchElementException();
		//将列表赋值给elementData
        Object[] elementData = ArrayList.this.elementData;
        //如果下一个元素的下表大于等于列表的长度,则抛异常
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        //将访问下一个元素的下标加1
        cursor = i + 1;
        //将i赋值给lastRet,并获取当前下标位置的元素
        return (E) elementData[lastRet = i];
    }

    //元素删除
    public void remove() {
        //如果遍历访问的最后一个元素的下标小于0,则抛异常
        if (lastRet < 0)
            throw new IllegalStateException();
        //列表预期修改次数与实际修改次数检查
        checkForComodification();

        try {
            //调用ArrayList的remove方法,删除下标等于迭代器当前访问元素的下标
            ArrayList.this.remove(lastRet);
            //迭代器当前访问元素的下标,赋值给当前的访问列表下一个元素的下标
            cursor = lastRet;
            //将迭代器当前访问元素的下标赋值为-1
            lastRet = -1;
            //将列表实际修改次数,与迭代器中的预期修改次数同步
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    //迭代器函数式遍历列表元素
    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        //列表长度
        final int size = ArrayList.this.size;
         //将访问列表下一个元素的下标赋值给i
        int i = cursor;
        //如果已经访问到最后一个元素,则程序停止
        if (i >= size) {
            return;
        }
        //将列表赋值给elementData
        final Object[] elementData = ArrayList.this.elementData;
        //如果下一个元素的下表大于等于列表的长度,则抛异常
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        //当下一个元素的下表不等于列表的长度,且列表预期修改次数与实际修改次数相等
        //迭代遍历列表元素
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        //在迭代结束时更新一次以减少堆写入流量
        //赋值访问列表下一个元素的下标为列表长度
        cursor = i;
        //赋值迭代遍历当前元素的下标为列表末尾
        lastRet = i - 1;
        //列表预期修改次数与实际修改次数检查
        checkForComodification();
    }

    //列表预期修改次数与实际修改次数检查
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMap的几种遍历方式如下: 1. Entry遍历:使用entrySet()方法可以同时遍历Map里面的Key和Value。可以通过迭代器或者foreach循环来实现。例如: ```java HashMap<String, Integer> map = new HashMap<>(); // 添加键值对 map.put("A", 1); map.put("B", 2); map.put("C", 3); // 使用entrySet()方法遍历 for (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); // 处理key和value } ``` 2. keySet遍历:使用keySet()方法可以遍历Map里面的Key。可以通过迭代器或者foreach循环来实现。例如: ```java HashMap<String, Integer> map = new HashMap<>(); // 添加键值对 map.put("A", 1); map.put("B", 2); map.put("C", 3); // 使用keySet()方法遍历 for (String key : map.keySet()) { Integer value = map.get(key); // 处理key和value } ``` 3. foreach遍历:在JDK8及以上版本中,可以直接使用foreach循环来遍历HashMap的键值对。例如: ```java HashMap<String, Integer> map = new HashMap<>(); // 添加键值对 map.put("A", 1); map.put("B", 2); map.put("C", 3); // 使用foreach遍历 map.forEach((key, value) -> { // 处理key和value }); ``` 4. keySet foreach遍历:在JDK8及以上版本中,可以使用keySet()方法获取键的集合,然后使用foreach循环遍历。例如: ```java HashMap<String, Integer> map = new HashMap<>(); // 添加键值对 map.put("A", 1); map.put("B", 2); map.put("C", 3); // 使用keySet foreach遍历 for (String key : map.keySet()) { Integer value = map.get(key); // 处理key和value } ``` 以上是HashMap的几种常见遍历方式。根据具体的需求,可以选择适合的遍历方式来操作HashMap的键值对。 #### 引用[.reference_title] - *1* *3* [Java - 关于HashMap通过keySet遍历kv的二次调用问题](https://blog.csdn.net/Zong_0915/article/details/120905738)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [HashMap的七种遍历方式](https://blog.csdn.net/maojian_ohhey/article/details/115431835)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值