注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

程序员小站

J2EE丨Spring | JVM | Scala

 
 
 

日志

 
 

集合迭代删除元素报ConcurrentModificationException错误分析  

2012-04-30 20:18:08|  分类: java技巧 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

迭代是Java集合中常见的操作,那么有一种是想在迭代时删除元素那么久会报错:Exception in thread "main" java.util.ConcurrentModificationException。但是。。。如果我想删除倒数第二个元素的时候,则不会报错!!!神奇么?接下来我们一步一步分析内部实现原理。
先看代码:

Collection<User> users = new ArrayList<User>();
users.add(new User(1,"aaa"));
users.add(new User(2,"bbb"));
users.add(new User(3,"ccc"));
Iterator<User> itr = users.iterator();
while(itr.hasNext()){
User user = itr.next();
if("aaa".equals(user.getName())){
users.remove(user);
}else{
System.out.println(user);
}
}

User是一个简单的类,只有int id,String name两个属性,分别实现set get方法,并根据id,name实现构造函数,覆写toString方法。在此不贴代码了。
执行代码,报错,然后把循环中的aaa换成bbb、ccc试试,就会印证我们前面说的效果。
为什么是这样呢?
控制台显示是如下语句报错,
User user = itr.next();
我们打开源码ArrayList源码(因为Iterator只是个接口,没有方法体),发现木有iterator()方法,接着找到父类AbstractList找到
public Iterator<E> iterator() {
return new Itr();
}
好,接着找到AbstractList内部类Itr,找到next()方法:第一句调用了 checkForComodification();方法:

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

其中expectedModCount是创建Itr时候的modCount,modCount属于AbstractList类:
protected transient int modCount = 0;
然后在ArrayList的remove和add方法里都能找到

modCount++;

也就是说只要添加或删除元素modCount都会增加,于是就知道modCount是一个版本号,如果得到得到Iterator时的版本号和当前版本不同,就会报错:java.util.ConcurrentModificationException。

那么为什么删除倒数第二个元素的时候不会报错呢?说明没有调用到next()方法呗!看我们的代码中,在调用next()之前,先调用了hasNext()方法判断是不是有下一个元素。好,我们找到Itr类的hasNext()代码:

public boolean hasNext() {
return cursor != size();
}

cursor是数组指针(List内部是用数组实现的),循环的时候指针一直+1,直到取到所有元素返回false。
size()是当前数据的元素个数。
按我们的例子说,当取到bbb时 cursor是1,size是3,删除后size变成2,再调用hasNext方法时,cursor变成2,与size相等!
于是返回false我们的代码就结束了,不会执行到next方法。
当删除ccc是,cursor是2,再调用hasNext方法时cursor是3,size是2,不相等,返回true,于是调用next方法,于是报错。。。

原因找到了,那么如何解决呢?
两种思路:1.不在Iterator里删除元素;2.找一个可以在在Iterator里删除的List实现类。
想看第一种思路,如果List存放的是简单类型,或者是能明确得到对象,直接调用users.remove();方法即可
如果想删除多个元素的话,可以先建立一个List保存要删除的元素,循环完成后调用users.removeAll();方法

Iterator<User> itr = users.iterator();
Collection<User> dels = new ArrayList<User>();
while(itr.hasNext()){
User user = itr.next();
if("ccc".equals(user.getName())){
// users.remove(user);
dels.add(user);
}else{
System.out.println(user);
}
}
users.removeAll(dels);

思路二:用 new CopyOnWriteArrayList<User>();代替 new ArrayList<User>();
CopyOnWriteArrayList是ArrayList 的一个线程安全的变体,其中所有可变操作(addset 等等)都是通过对底层数组进行一次新的复制来实现的。
  评论这张
 
阅读(390)| 评论(1)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017