一、背景

在以前的随笔中说道过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. Jumpserver部署与安装

    jumpserver特点: 完全开源,GPL授权 Python编写,容易再次开发 实现了跳板机基本功能,认证.授权.审计 集成了Ansible,批量命令等 支持WebTerminal Bootstra ...

  2. day2--课前考试题

    Linux 系统命令操作语法的格式: 命令        [参数选项]          [文件或路径] rm                      -f                      ...

  3. 基于Visual Studio 2010 阐述C#4个特性

    Csharp4.0与以往版本基础体现了强大的性能优势,主要体现在以下四个方面: 1. 通过委托成员来实现接口 在C# 4.0中可以通过委托来实现某个成员的接口,例如下面的代码: public clas ...

  4. C:\WINDOWS\system32\wmp.dll”受到“Windows 系统文件保护”

    在VC# 2005 中,要是打包的程序中包含了Windows Media Player 这个组件的话,在生成解决方案的过程中会提示出错:  "错误1,应将“wmp.dll”排除,原因是其源文 ...

  5. [转载] ZooKeeper的Java客户端API

    转载自 http://www.cnblogs.com/ggjucheng/p/3370359.html http://zookeeper.apache.org/doc/trunk/javaExampl ...

  6. SDRAM操作说明

    SDRAM是做嵌入式系统中,常用是的缓存数据的器件.基本概念如下(注意区分几个主要常见存储器之间的差异): SDRAM(Synchronous Dynamic Random Access Memory ...

  7. python 小白(无编程基础,无计算机基础)的开发之路 day2

    本节内容 列表.元组操作 字符串操作 字典操作 集合操作 文件操作 字符编码与转码 1. 列表.元组操作 列表是我们最以后最常用的数据类型之一,通过列表可以对数据实现最方便的存储.修改等操作 定义列表 ...

  8. tsung压力测试——tcp测试tsung.xml配置模版说明

    <?xml version="1.0"?> <!DOCTYPE tsung SYSTEM "/usr/local/share/tsung/tsung-1 ...

  9. 猪圈密码python脚本实现

    CTF比赛中,MISC题型中有时候会考到一种一种叫做"猪圈密码"(Pigpen_chiper)的简单加密方式.网上有个表可以对照地来实现解密,但是实际中太慢不符合竞速思维,于是写一 ...

  10. .net 分布式的未来:微服务

    一.背景&问题 之前框架是一个基于SOA思想设计的分布式框架.各应用通过服务方式提供使用,服务之间通信是RPC方式调用,具体实现基于.NET的WCF通信平台.框架存在如下2个问题: 1.高并发 ...