对JAVA集合进行遍历删除时务必要用迭代器
java集合遍历删除的方法:
1、当然这种情况也是容易解决,实现方式就是讲遍历与移除操作分离,即在遍历的过程中,将需要移除的数据存放在另外一个集合当中,遍历结束之后,统一移除。
2、使用Iterator遍历删除。
使用Iterator遍历删除的原因:
Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。
今天同事写了几行类似这样的代码:
public static void main(String args[]) {
List<String> famous = new ArrayList<String>();
famous.add("liudehua");
famous.add("madehua");
famous.add("liushishi");
famous.add("tangwei");
for (String s : famous) {
if (s.equals("madehua")) {
famous.remove(s);
}
}
}
运行出异常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at com.bes.Test.main(Test.java:15)
Java新手最容易犯的错误,对JAVA集合进行遍历删除时务必要用迭代器。切记。
其实对于如上for循环,运行过程中还是转换成了如下代码:
for(Iterator<String> it = famous.iterator();it.hasNext();){
String s = it.next();
if(s.equals("madehua")){
famous.remove(s);
}
}
仍然采用的是迭代器,但删除操作却用了错误的方法。如将famous.remove(s)改成it.remove()
则运行正常,结果也无误。
当然如果改成:
for (int i = 0; i < famous.size(); i++) {
String s = famous.get(i);
if (s.equals("madehua")) {
famous.remove(s);
}
}
这种方法,也是可以完成功能,但一般也不这么写。
为什么用了迭代码器就不能采用famous.remove(s)操作? 这种因为ArrayList与Iterator混合使用时会导致各自的状态出现不一样,最终出现异常。
我们看一下ArrayList中的Iterator实现:
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
基本上ArrayList采用size属性来维护自已的状态,而Iterator采用cursor来来维护自已的状态。
当size出现变化时,cursor并不一定能够得到同步,除非这种变化是Iterator主动导致的。
从上面的代码可以看到当Iterator.remove方法导致ArrayList列表发生变化时,他会更新cursor来同步这一变化。但其他方式导致的ArrayList变化,Iterator是无法感知的。ArrayList自然也不会主动通知Iterator们,那将是一个繁重的工作。Iterator到底还是做了努力:为了防止状态不一致可能引发的无法设想的后果,Iterator会经常做checkForComodification检查,以防有变。如果有变,则以异常抛出,所以就出现了上面的异常。
在java常用的集合框架就是list ,set ,map 。
list 可通过下标进行遍历,set,map不能通过下表进行遍历,因此对于set ,map的数据遍历时,常常采用迭代器,不过在使用迭代器移除数据时存在陷阱。
执行如下代码:
Set set = new HashSet();
set.add(1);
set.add(2);
set.add(3);
Iterator i = set.iterator();
while(i.hashNext()){
Object o = i.next();
set.remove(o);
}
执行结果会出现以下异常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
这个问题在集合框架使用迭代器遍历数据时存在,在java5新曾的foreach遍历方式下也存在。在通过下表遍历list的过程中不存在这个问题。
如:
List list = new ArrayList();
list .add(1);
list.add(2);
list.add(3);
for(int i = 0 ; i < list.size();i++){
list.remove(list.get(i));|| list.remove(i);
}
//或者使用iterator的remove方法
代码能够正常执行。
利用java迭代器Itetator遍历并删除HashMap中的元素问题
问题:
下面的代码试图利用HashMap的Iterator对象遍历该HashMap并删除满足条件的元素(比如超时的元素),但会抛出java.util.ConcurrentModificationException异常
public static void main(String[] args)
{
HashMap<String, String> hs=new HashMap();
hs.put("p1", "1");
hs.put("p2", "1");
hs.put("p3", "1");
hs.put("p4", "1");
hs.put("p5", "1");
hs.put("p6", "1");
Iterator it=hs.keySet().iterator();
while(it.hasNext())
{
String str=(String)it.next();
System.out.println(hs);
//逻辑处理.........
hs.remove(str);
}
}
原因应该是hs.remove(str)后,it内容没变,并且it里的指针列表需要重新排序,所以只要确保删除任一元素后,it保持同步更新即可:
解决方案一:删除任一元素后,it保持同步更新
Iterator it = hs.keySet().iterator();
while (it.hasNext()) {
it = hs.keySet().iterator(); //重新排序
String str = (String) it.next();
System.out.println(hs); // 逻辑处理.........
// .............
hs.remove(str);
}
// ...........
这样的时间复杂度明显太大(两层循环嵌套)
解决方案二:由于删除元素时,hs的iterator对象也重新排序,所以只要用hs的一个副本hsBack
Uackp的iterator去遍历hs即可,这样在删除hs元素时iterator就不会重排了(因为删除的是hs的元素,而不是该iterator所属的hsBackUackp)
//...................
Map hsBackUp = (HashMap<String, String>)hs.clone();
Iterator it = hsBackUp.keySet().iterator();
System.out.println(hsBackUp);
while(it.hasNext())
{
String str=(String)it.next();
System.out.println(hs);
hs.remove(str);
}
//.....................
这样虽然时间复杂度小了(只有一层循环),可是空间复杂度大了(多了一个hashmap的拷贝);
查阅api文档和相关资料后,原来iterator对象有一remove方法:
void remove()
Removes from the underlying collection the last element returned by the
iterator (optional operation). This method can be called only once per
call to next. The behavior of an iterator is unspecified if
the underlying collection is modified while the iteration is in
progress in any way other than by calling this method.
于是有下面的改进:
解决方案三:使用迭代器删除
//..............................
Iterator it=hs.keySet().iterator();
while(it.hasNext())
{
String str=(String)it.next();
System.out.println(hs);
it.remove();
}
//..............................
对JAVA集合进行遍历删除时务必要用迭代器的更多相关文章
- java 集合list遍历时删除元素
本文探讨集合在遍历时删除其中元素的一些注意事项,代码如下 import java.util.ArrayList; import java.util.Iterator; import java.util ...
- JAVA集合迭代遍历和特性介绍
数组.集合:都是一种容器,用一个对象管理多个对象:数组不能自动增长:只能存放同类型的元素 集合能自动扩容:部分集合允许存放不同类型的元素: 1.List: 有顺序的,允许存放重复的元素: 遍历:for ...
- java中List遍历删除元素-----不能直接 list.remove()
https://blog.csdn.net/github_2011/article/details/54927531 这是List接口中的方法,List集合调用此方法可以得到一个迭代器对象(Itera ...
- Java集合的遍历方式
Map的遍历 1.通过map.entrySet遍历Key和Value Map<Integer,Integer> map = new HashMap<>(); map.put(1 ...
- Java集合(4):Iterator(迭代器)
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构.迭代器通常被称为“轻量级”对象,因为创建它的代价小. Java中的Iterator功能比较简单, ...
- Java集合里的一些“坑”
这里主要谈下Java集合在使用中容易被忽略.又容易出现的两个“坑”,一个是集合与数组互相转换,另一个是集合遍历删除.主要通过代码演示. 一.集合与数组互相转换中的“坑” //Test1.java pa ...
- Java 集合详解 | 一篇文章解决Java 三大集合
更好阅读体验:Java 集合详解 | 一篇文章搞定Java 三大集合 好看的皮囊像是一个个容器,有趣的灵魂像是容器里的数据.接下来讲解Java集合数据容器. 文章篇幅有点长,还请耐心阅读.如只是为了解 ...
- java集合遍历删除指定元素异常分析总结
在使用集合的过程中,我们经常会有遍历集合元素,删除指定的元素的需求,而对于这种需求我们往往使用会犯些小错误,导致程序抛异常或者与预期结果不对,本人很早之前就遇到过这个坑,当时没注意总结,结果前段时间又 ...
- java集合类遍历删除方法测试以及使用场景记录
package test0; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java. ...
随机推荐
- java消息队列
来个个人通俗的解释吧.消息队列,顾名思义 首先是个队列.队列的操作有入队和出队 也就是你有一个程序在产生内容然后入队(生产者) 另一个程序读取内容,内容出队(消费者) 我想你应该是缺乏一个使用场景. ...
- iOS开发推送--客户端 服务端
1.推送过程简介 (1)App启动过程中,使用UIApplication::registerForRemoteNotificationTypes函数与苹果的APNS服务器通信,发出注册远程推送的申请. ...
- 世界上还有一个东西叫Virtual Pascal
官网是:http://web.archive.org/web/20060312064321/http://www.vpascal.com/news.php?item.16 不过2005年就不再维护了. ...
- JavaWeb笔记——Jsp的指令、内置对象和动作标签
JSP三大指令 一个jsp页面中,可以有0~N个指令的定义! 1. page --> 最复杂:<%@page language="java" info=" ...
- Android EditText获取光标位置并插入字符删除字符
1.获取光标位置 int index = editText.getSelectionStart(); 2.在光标处插入字符 int index = editText.getSelectionStart ...
- Linux上的运行的jar包
以调用json-simple为例 java程序(CsvTest.java) import org.json.simple.JSONObject; import java.util.*; public ...
- 常用Linux命令小结
常用Linux命令小结 Linux下有很多常用的很有用的命令,这种命令用的多了就熟了,对于我来说,如果长时间没有用的话,就容易忘记.当然,可以到时候用man命令查看帮助,但是,到时候查找的话未免有些临 ...
- javascript 简繁转换
js 简繁转换 function copy(ob) { var obj=findObj(ob); if (obj) { obj.select();js=obj.createTextRange();js ...
- Android开发之SD卡上文件操作
1. 得到存储设备的目录:/SDCARD(一般情况下) SDPATH=Environment.getExternalStorageDirectory()+"/"; 2. 判断SD卡 ...
- JavaScript —— 局部变量和全局变量
JS的全局变量有3种声明方式: 1.Function 外 var v_myVar; 2.Function 内 v_myVar; 3.window.v_myVar window.v_myVar 全局变量 ...