ArrayList在foreach正常迭代删除不报错的原因
一、背景
在以前的随笔中说道过ArrayList的foreach迭代删除的问题:ArrayList迭代过程删除问题
按照以前的说法,在ArrayList中通过foreach迭代删除会抛异常:java.util.ConcurrentModificationException
但是下面这段代码实际情况却没报异常,是什么情况?
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for (String item : list) {
System.out.println("item:" + item);
if ("1".equals(item)) {
list.remove(item);
}
}
二、分析
我们知道ArrayList的foreach迭代调用的是ArrayList内部类Itr,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
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
按照调用顺序查看源码
1.hasNext
ArrayList的foreach迭代首先调用的是ArrayList内部类Itr的hasNext(),该方法会对当前循环指针和长度做判断。只有当hasNext()返回true才会执行foreach里面的代码,cursor = size时候就退出循环(因为这两者相等意味着都遍历完了,假如ArrayList的size=2,那么hasNext会被调用3次,但是第3次调用不会执行foreach里面代码)。
2.如果上一步返回true的话会执行Itr的next(),如果数据无异常的话 cursor = i + 1;所以没执行一次next()时cursor都会+1
3.接着会执行到 list.remove(item),此处调用的是ArrayList的remove(Object o)而不是Itr的,看下ArrayList的remove()的源码:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
o != null,会进入else的fastRemove(index);可以看到ArrayList根据传入的值删除会进行遍历equals判断,找到索引再通过fastRemove(index)删除,因此List频繁做删除修改效率比较低。
4.再看下fastRemove()源码:
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
第8行会把该索引对应的数组的值置为null,并且size-1。但是却没有进行 cursor - 1操作
至此明白了。此处的ArrayList通过foreach迭代删除为什么不会报错:
刚开始ArrayList的size=2时,cursor =0
①第一次hasNext()进来,cursor != size进入next()后cursor=1,接着因为满足条件删除的时候size-1=1;
②第二次hasNext()进来,cursor = size = 1,所以不会执行foreach的代码,也不会出现后面检测modCount值抛ConcurrentModificationException
上述未抛异常的情况主要是hasNext()中判断遍历完成的条件与ArrayList删除后的数据刚好吻合而已。
所以只要满足条件:删除的元素在循环时的指针cursor+1=size就会出现这种情况!删除ArrayList倒数第二个(即第 size - 1个元素)就会出现不抛异常的假象。
(例如size=3,删除第2个元素;size=4,删除第3个元素)
因为删除后size-1=cursor
public boolean hasNext() {
return cursor != size;
}
hasNext()会返回false,不会进入ArrayList的迭代器,就不会进入 next() 执行checkForComodification()
这是一种条件判断下的特殊情况,其他情况都会抛出异常,所以不要在foreach进行删除数据。请在迭代器中进行删除。
ArrayList在foreach正常迭代删除不报错的原因的更多相关文章
- SharePoint 2013 删除母版页报错“This file may not be moved, deleted, renamed, or otherwise edited”
在使用SharePoint 2013母版页的时候,我复制了一个seattle.master页面,然后想重命名一下发现报错,删除也报错,spd.页面分别试过签入签出以后均报错,错误如下: 尝试找了一下错 ...
- elasticsearch删除索引报错【原】
如果elasticsearch删除索引报错 curl -X DELETE 'http://10.73.26.66:9200/httpd-34-2017.08.15' {"error" ...
- SpringBoot注册Windows服务和启动报错的原因
SpringBoot注册Windows服务和启动报错的原因 Windows系统启动Java程序会弹出黑窗口.黑窗口有几点不好.首先它不美观:其次容易误点导致程序关闭:但最让我匪夷所思的是:将鼠标光标选 ...
- ionic build android 中的报错详细原因以及解决方法
一.执行打包命令 ionic build android 1.报错: 原因: 其实也并非报错,但是会一直在下载gradle,由于网络或者其他原因,导致下载比较慢, 解决方案: 手动下载gradle,并 ...
- 服务端返回的json数据,导致前端报错的原因及解决方法
前言 最近在开发的过程中遇到了一个问题:后端传过来的json字符串不是标准的json字符串 导致报错的原因 后端传过来的json字符串中包含一些不标准的字符或错误的引号嵌套 1)\n 2) \r 3) ...
- Springboot 启动文件报错,原因是@ComponentScan写成了@ComponentScans
Springboot 启动文件报错,原因是@ComponentScan写成了@ComponentScans
- MariaDB:删除数据库报错:error: 'Error dropping database (can't rmdir './shiro', errno: 39)'
今天在删除一个库的时候报错,如下图所示. 删除命名:mysqladmin –u root –p drop shiro 解决办法: 删除./shiro目录下面的所有文件和目录. 重新执行删除命令即可!
- Docker删除镜像报错
问题描述: 笔者意图删除nginx-file的镜像文件,但通过命令删除镜像时出现报错信息,提示存在多个引用(即一个IMAGE可被多个REPOSITORY引用,故删除会出现失败),如下: [root@k ...
- java操作数组转list集合删除元素报错ConcurrentModificationException
public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>( ...
随机推荐
- eclipse使用maven tomcat插件部署无法关联源代码
一. 安装sourcelookup插件: 二. 在source lookup path里加入源码: 2.1) 加入项目源码或整个工作空间的源码(不加上连自己的代码都无法查看,默认是不加上的) 2.2) ...
- 【经验分享(续篇)】Trachtenberg system(特拉亨伯格速算系统)
之前有篇文章简单地介绍了Trachtenberg系统的乘法计算方法,地址在这里.针对一些特定的数字,Trachtenberg还发展出了更快的计算方法. 先来介绍乘数为11的速算方法.它的计算规则我们可 ...
- VS2013装扩展RazorGenerator
问题:vs2013工具扩展和更新搜索关键字:Razor Generator查找不到,如图 解决:下载网址:https://github.com/RazorGenerator/RazorGenerato ...
- Flex 基础语法(三)
2.flex-wrap 默认情况下,项目都排在一条线(又称"轴线")上.flex-wrap属性定义,如果一条轴线排不下,如何换行 属性 含义 nowrap(默认值) 不换行 wra ...
- CodeForces - 706B 二分stl
#include<iostream> #include<cstdio> #include<cstring> #include<string> #incl ...
- Android开发之漫漫长途 Ⅰ——Android系统的创世之初以及Activity的生命周期
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>中的相关知识,再次表示该书 ...
- js贪吃蛇-简单版
分享个用原生js写的贪吃蛇,最近在学java,按照当年写的 js的思路,转换成java,换汤不换药 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1 ...
- 使用GetThumbnailImage进行图片缩放操作
/// <summary> /// 获取等比例缩放图片的方法 /// </summary> /// <param name=&q ...
- Snow and Rainbow
缘分,让我们走到了一起.让这个美好的时刻美好的回忆记录在这里吧.
- ReentrantLock可重入锁的使用场景
摘要 从使用场景的角度出发来介绍对ReentrantLock的使用,相对来说容易理解一些. 场景1:如果发现该操作已经在执行中则不再执行(有状态执行) a.用在定时任务时,如果任务执行时间可能超过下次 ...