一个bug的解决过程,让你明白阅读源码的重要性
一个bug的解决过程,让你明白阅读源码的重要性
一. 背景
我们知道,有时候样本范围内的数据并不都是我们所需要的,某些情况下我们只需要其中的一部分。在这种情况下,我们在遍历样本时,就需要对取出的每一个样本数据进行判断,看看该样本是否满足我们的需要,对不需要的样本则要遍历删除。然而就是遍历删除这么简单的一件事情,我们在进行代码实现时也会暗含杀机,很多初学者在对集合进行遍历删除时会出现一些自己解决不了的异常。
举个例子,比如我们现在有一个List集合,样本数据是["a","b","b","c","d"],现在我们的需求是要将b这个元素从List集合中删除。当你看到这个需求时,脑海中是不是会觉得就这?这不就是一个简单的遍历删除吗?so easy!!然后你立马给出了N种实现方案,如下。
二. 实现方案一
很多小盆友给出的第一种解决方案,其实现代码如下:
但上面代码的执行结果,却会让你大跌眼镜,这个"b"怎么没删掉呢?如下图所示:
这是何解?
理论上来说,我们上面的代码执行逻辑可谓是天衣无缝,结果却大失所望。
其实之所以会出现这个问题,根源在于我们对数组的理解不够!我们知道数组是一片连续的内存空间,当我们从数组中移除元素时,数组是要进行内存拷贝保证内存连续性的,其源码如下:
这里解释一下,当i=1时,会将“b”元素删除;将第一个“b”元素删除后,后面的元素就会填充到角标为1的位置;而这时候i++,i的值为2,所以第二个“b”元素就成功逃逸了。上面的b之所以删除不掉,问题就在这!那怎么解决这个坑呢?
其实很简单,咱们看看能不能从数组的最后一个元素开始遍历,代码改写如下:
此时再次执行的结果如下:
漂亮,竟然解决了!那这次为什么就能解决呢?请你开动脑筋好好想想吧!!!
三. 实现方案二
代码实现
除了上面的实现方式之外,有的朋友还给出了第二种实现思路,代码如下:
上面的代码也很好理解,就是一个增强for循环。但该代码的执行结果更是让人大跌眼镜,不但没有把b元素给删除掉,人家竟然还抛异常了!他妹的,这是咋搞的?
原因剖析
这里老朱要给大家解释一下,咱们要明白增强for循环的底层其实就是在使用迭代器进行遍历,这就相当于我们在代码中使用迭代器的遍历,但错就错在使用了ArrayList的remove方法进行删除。那为什么用迭代器遍历就不能使用ArrayList的remove方法进行删除呢?如果你不知道其中缘由,老朱劝你还是要多读读源码,源码里面自有解答哦!
这是ArrayList的迭代器,有hasNext()、next()、remove()三个方法,这里我们重点看看next()方法的源码。next()方法的源码中,有一个check()检查,主要用于检查modCount与expectedModCount是否相等,如果不相等,就会直接抛出上面说到的并发修改异常。
那又为什么这两个变量会不相等呢?这就要对比迭代器的remove()和ArrayList的remove()方法了。在下图中,你看到了吗?迭代器的remove()方法中执行了expectedModCount = modCount操作,即让这两个变量相等,但ArrayList的remove()方法中却没有。
看到这个源码,我们应该就能想出解决上面异常的办法了吧!其实办法也很简单,如果我们要使用迭代器的遍历,那么删除时也要用迭代器的删除方法,而不能在用迭代器遍历时,使用ArrayList的remove()方法。即两者要配套,迭代器进行遍历,就用迭代器的删除。修改后的代码如下:
四. 小结
本文中,看似简单问题,却没想到后面会隐含着这么大的学问,所以各位小伙伴在平时的学习过程中,还是要多阅读源码,要不然你都不知道自己到底错在了哪里。对我们程序员来说,要做到”好读书,更求甚解“,这样才能让自己的水平变得更强!
相关推荐HOT
如何快速入门怎样学习java
要快速入门学习Java,你可以按照以下步骤进行:设置开发环境:首先,在你的计算机上安装Java开发工具包(JavaDevelopmentKit,JDK)。你可以从Ora...详情>>
2023-06-05 16:19:00SpringBoot整合JDBCTemplat
Spring Boot 是一个快速构建基于 Spring 框架的应用程序的工具,而 JdbcTemplate 是 Spring 框架中用于执行 SQL 语句的类。下面是 Spring Boot ...详情>>
2023-03-20 19:29:44举例说明什么情况下会更倾向于使用抽象类而不是接口?
接口和抽象类都遵循”面向接口而不是实现编码”设计原则,它可以增加代码的灵活性,可以适应不断变化的需求。下面有几个点可以帮助你回答这个问...详情>>
2022-11-07 14:34:20实例化创建对象有哪几种方式
1. new2. clone()3. 通过反射机制创建4. 序列化反序列化。反射示意代码:// Class.forName方法获取类,在调用类的newinstance()方法 Class cls ...详情>>
2022-11-07 14:33:23热门推荐
依赖注入实现原理?
沸如何快速入门怎样学习java
热SpringBoot整合JDBCTemplat
热一个bug的解决过程,让你明白阅读源码的重要性
新Redis的持久化机制有哪几种
高频面试题 - RabbitMQ如何防止重复消费问题
HashMap与ConcurrentHashMap的区别
线程和进程区别
JVM 是如何处理异常的?
如何实现跨域?说一下JSONP实现原理?
企业真实面试:父子类之间到底是怎么实例化的?
什么是 Spring Boot?Spring Boot 有哪些优点?
你了解过哪些同步器?请分别介绍下
新手java编程什么软件好