Iterator的remove方法可保证从源集合中安全地删除对象(转)

 如果对正在被迭代的集合进行结构上的改变(即对该集合使用add、remove或clear方法),那么迭代器就不再合法(并且在其后使用该迭代器将会有ConcurrentModificationException异常被抛出).

如果使用迭代器自己的remove方法,那么这个迭代器就仍然是合法的。

package chapter1;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Created by MyWorld on 2016/3/3.
 */
public class FastFailResolver {

    public static void main(String[] args) {
        Map<String, String> source = new HashMap<String, String>();
        for (int i = 0; i < 10; i++) {
            source.put("key" + i, "value" + i);
        }
        System.out.println("Source:" + source);
//        fastFailSceneWhenRemove(source);
        commonSceneWhenRemove(source);

    }

    private static void commonSceneWhenRemove(Map<String, String> source) {
        Iterator<Map.Entry<String, String>> iterator = source.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            if (entry.getKey().contains("1")) {
                iterator.remove();
            }
        }
        System.out.println(source);
    }

    private static void fastFailSceneWhenRemove(Map<String, String> source) {
        for (Map.Entry<String, String> entry : source.entrySet()) {
            if (entry.getKey().contains("1")) {
                source.remove(entry.getKey());
            }
        }
        System.out.println(source);
    }

}

 

 

 

 

 

3.在一个循环中删除一个列表中的元素

思考下面这一段在循环中删除多个元素的的代码


1

2

3

4

5

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));

for(int i=0;i<list.size();i++){

    list.remove(i);

}

System.out.println(list);

输出结果是:


1

[b,d]

在这个方法中有一个严重的错误。当一个元素被删除时,列表的大小缩小并且下标变化,所以当你想要在一个循环中用下标删除多个元素的时候,它并不会正常的生效。

与下面结合的一个示例:

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","a", "b",
                "c", "d"));
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals("a")) {
                list.remove(i);
            }
        }
        System.out.println(list);
    }

输出:

[a, b, c, d]

即输出与预期不一致

你也许知道在循环中正确的删除多个元素的方法是使用迭代,并且你知道java中的foreach循环看起来像一个迭代器,但实际上并不是。考虑一下下面的代码:


1

2

3

4

5

6

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));

for(String s:list){

    if(s.equals("a")){

        list.remove(s);

    }

}

它会抛出一个ConcurrentModificationException异常。 相反下面的显示正常:


1

2

3

4

5

6

7

8

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));

Iterator<String> iter = list.iterator();

while(iter.hasNext()){

        String s = iter.next();

        if(s.equals("a")){

            iter.remove();

    }

}

.next()必须在.remove()之前调用。在一个foreach循环中,编译器会使.next()在删除元素之后被调用,因此就会抛出ConcurrentModificationException异常,你也许希望看一下ArrayList.iterator()的源代码。

http://www.cnblogs.com/softidea/p/4279574.html

 

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorTest{
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("Test1");
        list.add("Test2");
        list.add("Test3");
        list.add("Test4");
        list.add("Test5");

        for(Iterator<String> it = list.iterator();it.hasNext();){
            if(it.next().equals("Test3")){
                it.remove();
            }
        }

        for(String s : list){
            System.out.println(s);
        }

    }
}

Iterator 支持从源集合中安全地删除对象,只需在 Iterator 上调用 remove() 即可。这样做的好处是可以避免 ConcurrentModifiedException ,这个异常顾名思意:当打开 Iterator 迭代集合时,同时又在对集合进行修改。
有些集合不允许在迭代时删除或添加元素,但是调用 Iterator 的remove() 方法是个安全的做法。

 

java.util.ConcurrentModificationException详解

http://blog.csdn.net/smcwwh/article/details/7036663

 

【引言】

经常在迭代集合元素时,会想对集合做修改(add/remove)操作,类似下面这段代码:

for (Iterator<Integer> it = list.iterator(); it.hasNext(); ) {
    Integer val = it.next();
    if (val == 5) {
        list.remove(val);
    }
}

运行这段代码,会抛出异常java.util.ConcurrentModificationException。

【解惑】

(以ArrayList来讲解)在ArrayList中,它的修改操作(add/remove)都会对modCount这个字段+1,modCount可以看作一个版本号,每次集合中的元素被修改后,都会+1(即使溢出)。接下来再看看AbsrtactList中iteraor方法

public Iterator<E> iterator() {
    return new Itr();
}

 

它返回一个内部类,这个类实现了iterator接口,代码如下:

private class Itr implements Iterator<E> {
    int cursor = 0;

    int lastRet = -1;

    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 的值
            expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }

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

在内部类Itr中,有一个字段expectedModCount ,初始化时等于modCount,即当我们调用list.iterator()返回迭代器时,该字段被初始化为等于modCount。在类Itr中next/remove方法都有调用checkForComodification()方法,在该方法中检测modCount == expectedModCount,如果不相当则抛出异常ConcurrentModificationException。

前面说过,在集合的修改操作(add/remove)中,都对modCount进行了+1。
在看看刚开始提出的那段代码,在迭代过程中,执行list.remove(val),使得modCount+1,当下一次循环时,执行 it.next(),checkForComodification方法发现modCount != expectedModCount,则抛出异常。

【解决办法】
如果想要在迭代的过程中,执行删除元素操作怎么办?
再来看看内部类Itr的remove()方法,在删除元素后,有这么一句expectedModCount = modCount,同步修改expectedModCount 的值。所以,如果需要在使用迭代器迭代时,删除元素,可以使用迭代器提供的remove方法。对于add操作,则在整个迭代器迭代过程中是不允许的。 其他集合(Map/Set)使用迭代器迭代也是一样。

 

 当使用 fail-fast iterator 对 Collection 或 Map 进行迭代操作过程中尝试直接修改 Collection / Map 的内容时,即使是在单线程下运行,  java.util.ConcurrentModificationException 异常也将被抛出。   
Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。
Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。

所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。
但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

 

有意思的是如果你的 Collection / Map 对象实际只有一个元素的时候, ConcurrentModificationException 异常并不会被抛出。这也就是为什么在 javadoc 里面指出: it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

 

时间: 2024-08-01 17:36:40

Iterator的remove方法可保证从源集合中安全地删除对象(转)的相关文章

jQuery中remove()方法用法实例_jquery

本文实例讲述了jQuery中remove()方法用法.分享给大家供大家参考.具体分析如下: 此方法将会从DOM中删除所有匹配的元素. 说明:remove()方法不会把匹配的元素从jQuery对象中删除,因而可以在将来再使用这些匹配的元素,不过除了这个元素本身得以保留之外,其他的比如绑定的事件,附加的数据等都会被移除. 语法结构: 复制代码 代码如下: $(selector).remove(expr) 参数列表: 参数 描述 expr 可选.用于筛选元素的jQuery表达式 实例代码: 实例一:

jQuery的remove()方法使用详解_jquery

remove()方法的定义和用法: 此方法将会从DOM中删除所有匹配的元素. 说明:remove()方法不会把匹配的元素从jQuery对象中删除,因而可以在将来再使用这些匹配的元素,不过除了这个元素本身得以保留之外,其他的比如绑定的事件,附加的数据等都会被移除. 语法结构: $(selector).remove(expr) 参数列表: 参数 描述 expr 可选.用于筛选元素的jQuery表达式 实例代码: <!DOCTYPE html> <html> <head> &

java中循环删除list中元素的方法总结_java

印象中循环删除list中的元素使用for循环的方式是有问题的,但是可以使用增强的for循环,然后今天在使用时发现报错了,然后去科普了一下,再然后发现这是一个误区.下面就来讲一讲..伸手党可直接跳至文末.看总结.. JAVA中循环遍历list有三种方式for循环.增强for循环(也就是常说的foreach循环).iterator遍历. 1.for循环遍历list for(int i=0;i<list.size();i++){ if(list.get(i).equals("del")

写了ocx控件 在网页中调用显示 对象不支持此属性或方法

问题描述 写了ocx控件 在网页中调用显示 对象不支持此属性或方法 我用的是 vs2010 用模版自动生成的只加了一个方法,然后自己写了一个vbs脚本调用这个方法正常,用的是控件名创建的对象.在网页中控件也创建成功的用的是classid方式创建的,但是调用这个方法就是提示对象不支持此属性或方法.我用activex control test 工具测试一切正常,也能看到这个方法,但是一到网页中就提示对象不支持此属性或方法 麻烦大家帮忙分析分析(我是初学者) 解决方案 应该是浏览器安全性阻止了控件的加

安卓意图-android中的intent对象的addCategory方法和setType方法有什么用区别

问题描述 android中的intent对象的addCategory方法和setType方法有什么用区别 android中的intent对象的addCategory方法和setType方法有什么用区别 按照语翻译的中文字面意思很相似啊, 解决方案 这个一两句说不清楚,你去看看资料,网上很多解释,主要是自己动手用用,别总看

NIO 不调用iterator的remove的问题

问题描述 NIO 不调用iterator的remove的问题 package chatIO; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketC

simpletree-SimpleTree源码中的展开节点方法,不知道怎么展开到指定的节点,请前段大神指教

问题描述 SimpleTree源码中的展开节点方法,不知道怎么展开到指定的节点,请前段大神指教 我现在要做的是在刷新组织树时,展开到指定的节点,我传了一个pnode(组织树中 的ID),但是不知道展开节点的方法是哪个,不知道在哪里做判断停止展开节点.下面是SimpleTree的js代码. 解决方案 下面是SimpleTree 的JS代码 /* jQuery SimpleTree Drag&Drop plugin Update on 22th May 2008 Version 0.3 * Lice

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6.x中不同,相同的则不再叙述. EntityFramework Core 1.1方法理论详解 当我们利用EF Core查询数据库时如果我们不显式关闭变更追踪的话,此时实体是被追踪的,关于变更追踪我们下节再叙.就像我们之前在EF 6.x中讨论的那样,不建议手动关闭变更追踪,对于有些特殊情况下,关闭变更追

Remove 方法

  从一个 Dictionary 对象中删除一个主键,条目对. object.Remove(key) 参数 object 必选项.总是一个 Dictionary 对象的名称. key 必选项. key 与要从 Dictionary 对象中删除的主键,条目对相关联. 说明 如果所指定的主键,条目对不存在,那么将导致一个错误. 下面这段代码说明了 Remove 方法的用法: var a, d, i, s; // 创建一些变量.d = new ActiveXObject("Scripting.Dict