遍历ArrayList时同时修改引发的问题
看见一篇博客,没有写完整,于是增补了一下:
博客原文:http://www.cnblogs.com/alipayhutu/archive/2012/08/11/2634073.html
注:黄色字体为我添的
CopyOnWriteArrayList,因何而存在?
ArrayList的一个线程安全的变体,其所有可变操作(add、set 等)都是通过对底层数组进行一次新的复制来实现的,代价昂贵。
CopyOnWriteArrayList,是因”并发”而生。
【场景一】对于ArrayList,使用直接方式,一边遍历,一边删除,会报错。
// 删除/修改元素for(String item : list){list.remove(item);}

原因:foreach语法是通过Iterator来实现的,当遍历这个List的时候,会生成一个ArrayList.Itr对象,这个私有内部类实现了Iterator接口,也就是说上面这段代码与下面这段代码效果一样:
Iterator it = list.iterator() ;
while(it.hasNext()){
String temp = it.next() ;
list.remove(temp) ;
}
在ArrayList中有一变量记录的当前这个ArrayList被修改的次数,每当调用add/remove方法就会把该参数的值加一,当生成Iterator对象时该对象会记录当前状态ArrayList的修改次数,然后在每次调用it.next()时就会判断当前ArrayList修改次数是否和它记录的相同,如果不同就抛出异常。例如上面等效后的代码,当执行 it = list.iterator() ;时 it对象记录在执行这句代码之前list对象的修改次数,当第一次执行it.next()语句时不会发生异常,因为这时list还没有被修改,但是当第二次执行it.next()时list已经通过list.remove(temp)代码修改了其内部的修改次数变量,所以导致it对象记录的修改次数和list的修改次数不同,所以就抛出了异常。
只需记住的准则是: 使用Iterator遍历集合时是不能修改集合的。
解决办法一:使用迭代器,一边遍历,一边删除,不会报错。
// 删除/修改元素
Iterator<String> it = list.iterator();
while(it.hasNext()){
String ele = it.next();
it.remove();
}
http://blog.sina.com.cn/s/blog_605f5b4f0100qsgf.html
使用Iterator的remove方法时不会对集合是否被更改进行判断,所以上面不会出现ModifyException。
解决办法二:使用CopyOnWriteArrayList,直接方式,一边遍历,一会删除,不会报错。
for(String item : list){
list.remove(item);
}
使用CopyOnWriteArrayList的remove方法时,该方法会先将List复制一份,对副本进行修改,然后把以前的List引用重新指向副本,所以不会出现异常。
【场景二】对于ArrayList,使用迭代器,一边遍历,一边add,会报错。
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
String tem = str + "...";
list.add(tem);
}原因:因为list.add会修改其内部存储的list修改次数变量,导致it内部记录的list修改次数和list当前状态的修改次数不同,所以产生异常。
解决办法一:改用CopyOnWriteArrayList,直接方式,一边遍历,一边add,不会报错。
for(String item : list){
String tem = item + "...";
list.add(tem);
}使用CopyOnWriteArrayList的add方法时,该方法会先将List复制一份,对副本进行修改,然后把以前的List引用重新指向副本,所以不会出现异常,
直接遍历与使用Iterator的效果是一样的。
解决办法二:改用CopyOnWriteArrayList,一边遍历,一边add,不会报错。
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
String tem = str + "...";
list.add(tem);
}
【场景三】对于CopyOnWriteArrayList,迭代器,不能remove。
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
String tem = str + "...";
it.remove();
}CopyOnWriteArrayList实现的remove方法中直接抛出异常,说明不支持该操作,至于为什么这样做还待以后慢慢体会。

解决办法一:使用直接方式,一边遍历,一边add/remove()
for(String item : list){
String tem = item + "...";
list.remove(item);
list.add(tem);
}
上面这个解决方案作者写错了,这段代码会抛出ModifyException异常。
【总结】底层究竟发生着什么?CPU、内存使用如何? to be continued…
遍历ArrayList时同时修改引发的问题的更多相关文章
- 写一段代码在遍历 ArrayList 时移除一个元素?
该问题的关键在于面试者使用的是 ArrayList 的 remove() 还是 Iterator 的 remove()方法.这有一段示例代码,是使用正确的方式来实现在遍历的过程中移 除元素,而不会出现 ...
- 遍历ArrayList数组时可能存在的问题
我们都知道ArrayList类中有个重要的方法是Add(),该方法用于向集合中添加元素,它有一个object类型的参数,表示通过该方法可以向集合中添加任意类型的项,由于ArrayList动态数组中的元 ...
- Java_LIST使用方法和四种遍历arrayList方法
1.List接口提供的适合于自身的常用方法均与索引有关,这是因为List集合为列表类型,以线性方式存储对象,可以通过对象的索引操作对象. List接口的常用实现类有ArrayList和Linked ...
- 使用ArrayList时代码内部发生了什么(jdk1.7)?
前言 ArrayList(这里的ArrayList是基于jdk1.7)是在项目中经常使用的集合类,例如我们从数据库中查询出一组数据.这篇文章不去剖析它的继承和实现,只是让我们知道实例化及增删改查时它的 ...
- Java集合——遍历集合元素并修改
Java集合——遍历集合元素并修改 摘要:本文主要总结了遍历集合的方式,以及在遍历时修改集合要注意的问题. 遍历Collection 对List和Set的遍历,有四种方式,下面以ArrayList为例 ...
- Lambda 表达式遍历集合时用remove方法删除list集合中满足条件的元素问题
一:循环遍历list集合的四种方式 简单for循环 iterator循环 增加for循环 Lanbda表达式 二:四种遍历方式的用法示例 //简单for循环 List<SalaryAdjustm ...
- php 中遍历数组时使用引用出现的问题
今天在使用foreach遍历数组时发现,当使用&时会出现问题: $arr = array( array('id' => 100, 'error'=> 'aa'), array('i ...
- 迭代器 iterator(二): 用iterator遍历arraylist
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址.迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些 ...
- 【ITOO 2】使用ArrayList时的注意事项:去除多余的null值
问题描述:在课表导入的时候,将数据从excel表里读出,然后将list批量插入到对应的课程表的数据表单中去,出现结果:当我们导入3条数据时,list.size()为3,但是实际上,list里面存在10 ...
随机推荐
- URL处理几个关键的函数parse_url、parse_str与http_build_query
parse_url() 该函数可以解析 URL,返回其组成部分.它的用法如下: array parse_url(string $url) 此函数返回一个关联数组,包含现有 URL 的各种组成部分.如果 ...
- 给CentOS6.3 + PHP5.3 安装PHP性能测试工具 XHProf-0.9.2
一.什么是XHProf XHProf官网:http://pecl.php.net/package/xhprof XHProf是一个分层PHP性能分析工具.它报告函数级别的请求次数和各种指标,包括 阻塞 ...
- Linux文件查找命令 find 详解
关于find命令 由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下.即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权 ...
- c++笔记整理
一:导读 假设编写了一个C++程序,如何让他允许起来呢,这取决于计算机环境和所使用的C++编译器. 1.使用文本编辑器编写程序,并将其保存在文档中,====此就是源代码 2.编译源代码,编译过程就意味 ...
- Ubuntu GNOME 16.10 Beta 1问世了!
导读 Ubuntu GNOME 16.10操作系统已经进入研发周期一段时间了,今天终于可以下载Beta 1版本进行测试了.作为Ubuntu官方flavor之一,Ubuntu GNOME团队非常努力的整 ...
- 介绍一个非常好用的跨平台C++开源框架:openFrameworks
介绍一个非常好用的跨平台C++开源框架:openFrameworks 简介 首先需要说明的一点是: openFrameworks 设计的初衷不是为计算机专业人士准备的, 而是为艺术专业人士准备的, 就 ...
- Python:设计模式介绍--单例模式
单例模式 1.单例是只有一个实例2.通过静态字段+静态字段伪造出一个单例效果3.什么时候用:当所有实例中封装的数据相同时,创建单例模式(eg:连接池) 用单例模式创建连接池: class CP: __ ...
- .NET LINQ 聚合操作
聚合操作 聚合运算从值集合计算单个值. 从一个月的日温度值计算日平均温度就是聚合运算的一个示例. 方法 方法名 说明 C# 查询表达式语法 Visual Basic 查询表达式语法 更多信息 ...
- .NET 清理非托管资源
Dispose 类型的 Dispose 方法应释放它拥有的所有资源.它还应该通过调用其父类型的 Dispose 方法释放其基类型拥有的所有资源.该父类型的 Dispose 方法应该释放它拥有的所有资源 ...
- NEFU 558 迷宫寻路
题目链接 简单搜索题 #include <cstdio> #include <iostream> #include <cstring> using namespac ...