遍历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 ...
随机推荐
- ajax请求webservice的过程中遇到的问题总结
前台用ajax的post方法,无法请求到webservice中的方法的时候,需要在配置文件中添加 web.config文件中的 <system.web> 节点下加入:<webServ ...
- C#高级编程笔记 Day 5, 2016年9月 13日 (泛型)
[重点]泛型:有了泛型,就可以创建独立于被包含类型的类和方法了.我们不必给不同的类型编写功能相同的许多方法和类,只创建一个方法或类即可,以下是泛型的特点:性能.类型安全性.二进制代码重用.代码的扩展. ...
- StartUML2.8破解
StarUML官方下载地址:http://staruml.io/download StarUML是一个非常好用的画UML图的工具,但是它是收费软件,以下是破解方法: 1.使用Editplus或者N ...
- dex文件格式二
一. dex文件头 (1) magic value 在DexFile.c dexFileParse函数中 会先检查magic opt 啥是magic opt呢? 我们刚刚从cache目录拷贝出来的 ...
- stl vector erase
C++ Code 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 ...
- mac os x 启用apache 和 php
Mac OS X 是自带 Apache 和 PHP 的,但默认情况下并没有开启,此文说明如何启用这两个服务,环境基于 Mac OS X 10.6 Snow Leopard. 启动 Apache 命令行 ...
- 让EF飞一会儿:如何用Entity Framework 6 连接Sqlite数据库
获取Sqlite 1.可以用NuGet程序包来获取,它也会自动下载EF6 2.在Sqlite官网上下载对应的版本:http://system.data.sqlite.org/index.html/do ...
- 解决log4j:WARN No appenders could be found for logger
这个问题是因为我们的log4j.properties文件配置不够完整,所以我们给它配置齐了就不会再出现这个问题. log4j.properties不完整配置如下: log4j.rootLogger=D ...
- _stdcall,_cdecl区别
(1) _stdcall调用 _stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈. WIN32 Api都采用_stdcall调用方式,这样的宏定 ...
- 事务日志以及虚拟日志文件(VLFs)概述
Part 1:事务日志 每个 SQL Server 数据库都具有事务日志,用于记录所有事务以及每个事务对数据库所做的修改.必须定期截断事务日志以避免它被填满.但是,一些因素可能延迟日志截断,因此监视日 ...