一、背景

在以前的随笔中说道过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内部类ItrhasNext(),该方法会对当前循环指针和长度做判断。只有当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正常迭代删除不报错的原因的更多相关文章

  1. SharePoint 2013 删除母版页报错“This file may not be moved, deleted, renamed, or otherwise edited”

    在使用SharePoint 2013母版页的时候,我复制了一个seattle.master页面,然后想重命名一下发现报错,删除也报错,spd.页面分别试过签入签出以后均报错,错误如下: 尝试找了一下错 ...

  2. elasticsearch删除索引报错【原】

    如果elasticsearch删除索引报错 curl -X DELETE 'http://10.73.26.66:9200/httpd-34-2017.08.15' {"error" ...

  3. SpringBoot注册Windows服务和启动报错的原因

    SpringBoot注册Windows服务和启动报错的原因 Windows系统启动Java程序会弹出黑窗口.黑窗口有几点不好.首先它不美观:其次容易误点导致程序关闭:但最让我匪夷所思的是:将鼠标光标选 ...

  4. ionic build android 中的报错详细原因以及解决方法

    一.执行打包命令 ionic build android 1.报错: 原因: 其实也并非报错,但是会一直在下载gradle,由于网络或者其他原因,导致下载比较慢, 解决方案: 手动下载gradle,并 ...

  5. 服务端返回的json数据,导致前端报错的原因及解决方法

    前言 最近在开发的过程中遇到了一个问题:后端传过来的json字符串不是标准的json字符串 导致报错的原因 后端传过来的json字符串中包含一些不标准的字符或错误的引号嵌套 1)\n 2) \r 3) ...

  6. Springboot 启动文件报错,原因是@ComponentScan写成了@ComponentScans

    Springboot 启动文件报错,原因是@ComponentScan写成了@ComponentScans

  7. MariaDB:删除数据库报错:error: 'Error dropping database (can't rmdir './shiro', errno: 39)'

    今天在删除一个库的时候报错,如下图所示. 删除命名:mysqladmin –u root –p  drop shiro 解决办法: 删除./shiro目录下面的所有文件和目录. 重新执行删除命令即可!

  8. Docker删除镜像报错

    问题描述: 笔者意图删除nginx-file的镜像文件,但通过命令删除镜像时出现报错信息,提示存在多个引用(即一个IMAGE可被多个REPOSITORY引用,故删除会出现失败),如下: [root@k ...

  9. java操作数组转list集合删除元素报错ConcurrentModificationException

    public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>( ...

随机推荐

  1. A Walk Through the Forest

    A Walk Through the Forest Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/O ...

  2. 一次线上Mysql数据库崩溃事故的记录

    文章简介 工作这几年,技术栈在不断更新,项目管理心得也增加了不少,写代码的速度也在提升,感觉很欣慰,毕竟是在一直进步,但是过程中也有许许多多的曲折,也踩过了数不尽的坑坑洼洼,从一个连百度都不知道用的萌 ...

  3. SVG绘制loading效果

    <div class="loading"> <svg width='40px' height='40px' xmlns="http://www.w3.o ...

  4. Android中常用的URI

    使用URI需要注意:当应用需要和手机里的文件或者程序互动时需要为该应用增加权限.在AndroidManifiest.xml文件中的根元素中增加如下配置 例如: 1.当应用需要读取.添加联系人时: 授予 ...

  5. 通讯框架 t-io 学习——websocket 部分源码解析

    前言 前端时间看了看t-io的websocket部分源码,于是抽时间看了看websocket的握手和他的通讯机制.本篇只是简单记录一下websocket握手部分. WebSocket握手 好多人都用过 ...

  6. ANDROID基础ACTIVITY篇之Activity的生命周期(一)

    首先我们先来看一下官方的Android的生命周期图: 根据这个流程图我们可以看到Activity的生命周期一共有7个方法,那么接下来我们就来聊聊这些方法执行过程. 首先在两个Activity(Main ...

  7. MySQL中Left Join和Right Join的理解

    虽然之前一直见过两个Join,对于其具体的含义也在参考书上读过,但是一直没有记住.现在换一种方式进行学习,改为实验方式理解. Left Join 测试表: 表结构很简单,test包括两个int字段,t ...

  8. Oracle单行函数基础运用

    单行函数 整个SQL的精髓:select语句+单行函数(背) 字符串函数 常用的处理字符串的函数有如下: No. 函数名 含义 1 UPPER(c1)  upper 将字符串全部转为大写 2 LOWE ...

  9. CSharpGL(47)你好,Framebuffer!

    CSharpGL(47)你好,Framebuffer! Framebuffer对象(FBO)是一种复杂的OpenGL对象.使用自定义的framebuffer,可以实现离屏渲染,进而实现很多高级功能,例 ...

  10. .NET之RabbitMQ学习笔记(二)-安装

    安装 1.安装erlang语言环境 因为rabbitmq是基于erlang进行开发,所以需要安装相应的依赖环境,学习中用到的erlang包下载地址:http://www.erlang.org/down ...