当前位置: 首页 > news >正文

源码角度分析Java 循环中删除数据为什么会报异常

一、源码角度分析Java 循环中删除数据为什么会报异常

相信大家在之前或多或少都知道 Java 中在增强 for中删除数据会抛出:java.util.ConcurrentModificationException 异常,例如:如下所示程序:

public class RmTest {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("001");list.add("002");list.add("003");list.add("004");for (String l : list) {if (Objects.equals(l, "002") || Objects.equals(l,"003")) {list.remove(l);}}System.out.println(list);}
}

运行后会发现抛出了异常:

在这里插入图片描述

特别是一些新手小伙伴一不注意就陷入其中,当然解决方法也特别简单,可以转为迭代器,然后使用迭代器的 remove 方式删除数据,或者使用循环下标的方式通过下标进行删除,但需要注意正向循环和反向循环,如果是正向循环的话需要注意计算下标位置,不过不要担心,下面我们都会一一进行介绍。

首先来分析下为什么在增强 for 中会出现java.util.ConcurrentModificationException 异常,这里现将java编译成class形式,看增强 for最终是以何种形式执行的:

javac RmTest.java

编译后的内容如下:

public class RmTest {public RmTest() {}public static void main(String[] var0) {ArrayList var1 = new ArrayList();var1.add("001");var1.add("002");var1.add("003");var1.add("004");Iterator var2 = var1.iterator();while(true) {String var3;do {if (!var2.hasNext()) {System.out.println(var1);return;}var3 = (String)var2.next();} while(!Objects.equals(var3, "002") && !Objects.equals(var3, "003"));var1.remove(var3);}}
}

可以看到增强for最终是编译成迭代器的方式进行遍历数据,但需要注意的是删除数据依然使用的 List 中的 remove 方法,通过抛出的异常链可以看出,问题发生在了 next 方法中的 checkForComodification 方法下:

在这里插入图片描述

下面看到 ArrayList 下迭代器的 next 方法中,在 Itr 类下:

在这里插入图片描述
在这个方法中首先调用了 checkForComodification 方法,正好上面的异常链中也涉及到了 checkForComodification 方法,下面进到该方法中:

在这里插入图片描述
这里是不是看到了熟悉的 ConcurrentModificationException 异常,只要 modCountexpectedModCount 不相等就会抛出该异常,下面看下 expectedModCount 的声明位置:

在这里插入图片描述

在迭代器内部声明的,并且起始值等于 modCount,而 modCount 则在定义在 AbstractList 在迭代器的外部,这里还记得前面迭代器中使用的是 List 中的 remove 方法删除的数据,这里看到该方法中:

在这里插入图片描述
该方法实际的删除逻辑在 fastRemove 方法中,继续看到该方法下:

在这里插入图片描述
看到这里是不是很直观了,modCount 数值发生了变化,而迭代器中的expectedModCount 没有随之修改,就导致 expectedModCount != modCount 而抛出异常。

我们都知道使用迭代器中的 remove 方式是不会引发异常的,比如:

public class RmTest {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("001");list.add("002");list.add("003");list.add("004");Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String l = iterator.next();if (Objects.equals(l, "002") || Objects.equals(l, "003")) {iterator.remove();}}System.out.println(list);}}

运行结果:

在这里插入图片描述

为什么迭代器的 remove 可以呢,下面看到该方法中:

在这里插入图片描述

可以看出迭代器的 remove 同样也是使用了 List 中的 remove 方法,但它会在删除后重置 expectedModCount 的值,使其保持和 modCount 一致,因此就不会触发上面的异常。

看到这里应该明白为什么会抛出异常了,但为什么这样设计呢?这里可以总结下其中,modCount主要表示集合被修改的次数,expectedModCount表示迭代器内部维护的集合被修改的次数。当modCountexpectedModCount不相等时,则表示肯定有其他某个地方对集合进行了修改,此时,如果继续使用迭代器遍历集合,就可能会出现遍历到非预期的元素或者下个元素不存在了,因此只要expectedModCountmodCount保持一致,数据就可认为是可信的。

通过这里也能给我们警醒,如果需要在并发情况下操作集合一定要选用线程安全的集合。

下面再补充下如果不用增强for,使用下标自增的方式删除是否可行吗?

public class RmTest {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("001");list.add("002");list.add("003");list.add("004");for (int i = 0; i < list.size(); i++) {String l = list.get(i);if (Objects.equals(l, "002") || Objects.equals(l,"003")) {list.remove(i);}}System.out.println(list);}
}

运行后:

在这里插入图片描述

发现 003 并没有被移除,因为当移除了 002 后,002 后的数据顺势向前移位,原本003的下标为 2 ,移位后变成了 1 ,但下标 i 继续增长,便会错过后面的数据,那怎么解决呢,既然后面的数据向前移位,对下标i也向前移位就是了:

public class RmTest {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("001");list.add("002");list.add("003");list.add("004");for (int i = 0; i < list.size(); i++) {String l = list.get(i);if (Objects.equals(l, "002") || Objects.equals(l,"003")) {list.remove(i);i = i-1;}}System.out.println(list);}
}

运行后数据正常:

在这里插入图片描述

既然正向遍历下标需要移位,那如果反过来反向循环不就可以不管下标了吗:

public class RmTest {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("001");list.add("002");list.add("003");list.add("004");for (int i = list.size() - 1; i >= 0; i--) {String l = list.get(i);if (Objects.equals(l, "002") || Objects.equals(l, "003")) {list.remove(i);}}System.out.println(list);}
}

运行后数据正常:

在这里插入图片描述

相关文章:

源码角度分析Java 循环中删除数据为什么会报异常

一、源码角度分析Java 循环中删除数据为什么会报异常 相信大家在之前或多或少都知道 Java 中在增强 for中删除数据会抛出&#xff1a;java.util.ConcurrentModificationException 异常&#xff0c;例如&#xff1a;如下所示程序&#xff1a; public class RmTest {public sta…...

leetCode 229. 多数元素 II + 摩尔投票法 + 进阶 + 优化空间

229. 多数元素 II - 力扣&#xff08;LeetCode&#xff09; 给定一个大小为 n 的整数数组&#xff0c;找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。 进阶&#xff1a;尝试设计时间复杂度为 O(n)、空间复杂度为 O(1)的算法解决此问题。 &#xff08;1&#xff09;哈希表 class …...

5 个编写高效 Makefile 文件的最佳实践

在软件开发过程中&#xff0c;Makefile是一个非常重要的工具&#xff0c;它可以帮助我们自动化构建、编译、测试和部署。然而&#xff0c;编写高效的Makefile文件并不是一件容易的事情。在本文中&#xff0c;我们将讨论如何编写高效的Makefile文件&#xff0c;以提高我们的开发…...

20231028刷题记录

P3381 【模板】最小费用最大流 Portal. sol. 注意 SPFA 找最小费用增广路时不要到终点就返回&#xff0c;因为到终点的路径可能有多条不能确定哪条是费用最小的。 P2740 [USACO4.2] 草地排水Drainage Ditches Portal. 最大流模板。 注意区分 N , M N,M N,M。 CF609D G…...

39 深度学习(三):tensorflow.data模块的使用(基础,可跳)

文章目录 data模块的使用基础api的介绍csv文件tfrecord data模块的使用 在训练的过程中&#xff0c;当数据量一大的时候&#xff0c;我们纯读取一个文件&#xff0c;然后每次训练都调用相同的文件&#xff0c;然后进行处理是很不科学的&#xff0c;或者说&#xff0c;当我们需…...

css四种导入方式

1 行内样式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <h1 style"color: blue">我是标题</h1> </body> </htm…...

Linux学习第24天:Linux 阻塞和非阻塞 IO 实验(一): 挂起

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 在正式开始今天的笔记之前谈一下工作中遇见的一个问题。 本篇笔记主要学习Linux 阻塞和非阻塞 IO 实验&#xff0c;主要包括阻塞和非阻塞简介、等待队列、轮询、…...

037-第三代软件开发-系统音量设置

第三代软件开发-系统音量设置 文章目录 第三代软件开发-系统音量设置项目介绍系统音量设置QML 实现C 实现 总结一下 关键字&#xff1a; Qt、 Qml、 volume、 声音、 GPT 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&#xff08;Qt Meta-Obj…...

Python 自动化详解(pyautogui)

文章目录 1 概述1.1 第三方库&#xff1a;pyautogui1.2 坐标说明 2 操作对象2.1 鼠标2.1.1 定位2.1.2 移动2.1.3 拖动2.1.4 滚动2.1.5 点击 2.2 键盘2.2.1 输入2.2.2 按键2.2.3 快捷键 2.3 屏幕2.3.1 截图2.3.2 分辨率 2.4 信息提示2.4.1 提示框2.4.2 选择框2.4.3 密码输入2.4.…...

【Linux】第四站:Linux基本指令(三)

文章目录 一、时间相关的指令1.指令简介2.使用 二、cal指令三、find指令 -name1.介绍2.使用 四、grep指令1.介绍2.使用 五、zip/unzip指令1.介绍2.zip的安装3.使用 六、tar指令&#xff1a;打包解包&#xff0c;不打开它、直接看内容1.介绍2.使用 七、bc指令八、uname -r指令1.…...

SpringBoot内置工具类之断言Assert的使用与部分解析

先例举一个service的demo中用来验证参数对象的封装方法&#xff0c;使用了Assert工具类后是不是比普通的 if(xxx) { throw new RuntimeException(msg) } 看上去要简洁多了&#xff1f; 断言Assert工具类简介 断言是一个判断逻辑&#xff0c;用来检查不该发生的情况&#xff…...

如何检测租用的香港服务器是不是CN2线路呢?

CN2&#xff0c;是中国电信新一代融合承载网络&#xff0c;是为电信自身关键业务和具有QoS保证的SLA业务服务的&#xff0c;可以提供高性能的网络指 标&#xff0c;平均单向时延、最大单向时延、单向丢包率等均属于顶尖水平。简单地说&#xff0c;CN2和普通网络&#xff0c;就像…...

Spring Boot进阶(94):从入门到精通:Spring Boot和Prometheus监控系统的完美结合

&#x1f4e3;前言 随着云原生技术的发展&#xff0c;监控和度量也成为了不可或缺的一部分。Prometheus 是一款最近比较流行的开源时间序列数据库&#xff0c;同时也是一种监控方案。它具有极其灵活的查询语言、自身的数据采集和存储机制以及易于集成的特点。而 Spring Boot 是…...

Redis(02)| 数据结构-SDS

一、键值对数据库是怎么实现的&#xff1f; 在开始讲数据结构之前&#xff0c;先给介绍下 Redis 是怎样实现键值对&#xff08;key-value&#xff09;数据库的。 Redis 的键值对中的 key 就是字符串对象&#xff0c;而 value 可以是字符串对象&#xff0c;也可以是集合数据类型…...

HackTheBox-Starting Point--Tier 0---Preignition

文章目录 一 题目二 实验过程 一 题目 Tags Web、Custom Applications、Apache、Reconnaissance、Web Site Structure Discovery、Default Credentials译文&#xff1a;Web、定制应用程序、Apache、侦察、网站结构发现、默认凭证Connect To attack the target machine, you …...

售货机相关的电路

一、货道选通矩阵电路&#xff0c;类似扫描电路&#xff0c;驱动哪个电机&#xff0c;就打开相应的行线与列线输出 二、MDB纸币器&#xff0c;虽然现在国内都是手机支付&#xff0c;但如果机器还是外销国外还是有用 三、硬币器电路&#xff0c;投币与退币&#xff0c;脉冲信号…...

软考高项(十四)项目沟通管理 ★重点集萃★

&#x1f451; 个人主页 &#x1f451; &#xff1a;&#x1f61c;&#x1f61c;&#x1f61c;Fish_Vast&#x1f61c;&#x1f61c;&#x1f61c; &#x1f41d; 个人格言 &#x1f41d; &#xff1a;&#x1f9d0;&#x1f9d0;&#x1f9d0;说到做到&#xff0c;言出必行&am…...

Linux多线程服务端编程:使用muduo C++网络库 学习笔记 第五章 高效的多线程日志

“日志&#xff08;logging&#xff09;”有两个意思&#xff1a; 1.诊断日志&#xff08;diagnostic log&#xff09;。即log4j、logback、slf4j、glog、g2log、log4cxx、log4cpp、log4cplus、Pantheios、ezlogger等常用日志库提供的日志功能。 2.交易日志&#xff08;trasac…...

利用Pholcus框架提取小红书数据的案例分析

前言 在当今互联网时代&#xff0c;数据的获取和分析变得越来越重要。爬虫技术作为一种数据采集的方法&#xff0c;被广泛涉及各个领域。在本文中&#xff0c;我们将介绍如何使用Python Spark语言和Pholcus框架来实现一本小红书数据爬虫的案例分析。 开发简述 Go语言作为一种…...

超详细Hadoop安装教程(单机版、伪分布式)

超详细Hadoop安装教程&#xff08;单机版、伪分布式&#xff09; 1.Hadoop分布式系统基础架构介绍1.1.Hadoop核心 2.Hadoop安装教程2.1.环境准备2.2.配置用户ssh 免密登录2.3.JAVA环境的安装和配置2.4.Hadoop安装2.5.单机版Hadoop配置2.6.伪分布式Hadoop配置2.7Hadoop初始化 1.…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...