Java 从CopyOnWriteArrayList中删除元素

Java 从CopyOnWriteArrayList中删除元素,java,copy-on-write,Java,Copy On Write,当我尝试使用迭代器从CopyOnWriteArrayList中删除元素时,出现了一个异常。 我注意到它被记录在案 不支持迭代器本身的元素更改操作(删除、设置和添加)。这些方法引发UnsupportedOperationException (来自) 现在,令人惊讶的是,我可以用foreach迭代它并使用remove()函数。但是我得到了一个著名的错误——当试图使用for循环从列表中删除一个项目时——您跳过了删除的元素旁边的元素。 那有什么建议吗 编辑:我是个白痴。我错过了一个事实,这是一个副本上写

当我尝试使用迭代器从CopyOnWriteArrayList中删除元素时,出现了一个异常。 我注意到它被记录在案

不支持迭代器本身的元素更改操作(删除、设置和添加)。这些方法引发UnsupportedOperationException

(来自)

现在,令人惊讶的是,我可以用foreach迭代它并使用remove()函数。但是我得到了一个著名的错误——当试图使用for循环从列表中删除一个项目时——您跳过了删除的元素旁边的元素。
那有什么建议吗

编辑:我是个白痴。我错过了一个事实,这是一个副本上写的名单,所以每次删除意味着一个新的副本。因此,如果有多个删除,我下面的建议可能是次优的

对于迭代器不支持删除的任何其他列表,或者任何不使用迭代器的列表,都是如此。要避免此错误,需要考虑三种基本技术:


  • 删除某些内容后减小索引(注意在下一次迭代之前不要对索引执行任何操作)。为此,您显然必须使用
    For(int i=0;i对集合进行迭代,选择要删除的所有元素并将其放入临时集合。迭代完成后,使用removeAll方法从原始集合中删除所有找到的元素


    这对您有效吗?我的意思是,不确定删除逻辑是否比算法中的逻辑更复杂。

    通常,您会先在单独的列表中迭代要删除的元素集,然后在for each循环之外删除它们(无论如何,这是基于迭代器的伪装循环)

    类似于这样的内容:

    int pos = 0;
    while(pos < lst.size() ) {
      Foo foo = lst.get(pos);
      if( hasToBeRemoved(foo) ) {
        lst.remove(pos);
        // do not move position
      } else {
        pos++;
      }
    }
    
    int pos=0;
    而(位置
    您可以使用而不是列表

    private Queue<Something> queue = new ConcurrentLinkedQueue<Something>();
    
    private Queue Queue=new ConcurrentLinkedQueue();
    

    它是线程安全的,支持
    迭代器。remove()
    。不过,请注意队列迭代器的线程安全行为(请查看javadoc)。

    因为这是一个
    CopyOnWriteArrayList
    所以在使用
    forEach
    进行迭代时删除元素是完全安全的。不需要花哨的算法

    list.forEach(e -> {
        if (shouldRemove(e))
            list.remove(e);
    });
    

    编辑:如果您想通过引用而不是位置删除元素,那么这当然有效。

    最短、最有效的方法:

    List<String> list = new CopyOnWriteArrayList<>();
    list.removeIf(s -> s.length() < 1);
    
    List List=new CopyOnWriteArrayList();
    list.removeIf(s->s.length()<1);
    
    它在内部创建一个长度相同的临时数组,并复制谓词返回true的所有元素


    请记住,如果您使用此方法实际迭代元素以执行某些操作,则这些操作将无法并行执行,因为removeIf调用是原子的,并且将锁定其他线程的遍历。下面的

    可与CopyOnWriteArrayList配合使用

    for(String key : list) {
        if (<some condition>) {
            list.remove(key);
        }
    }
    
    for(字符串键:列表){
    如果(){
    列表。删除(键);
    }
    }
    
    如果要删除所有元素,请使用just clear()。如果要保留元素,请将它们放在临时数组列表中,然后从中取回

    List<Object> tKeepThese= new ArrayList<>();
    for(ListIterator<Object> tIter = theCopyOnWriteArrayList; tIter.hasNext();)
    {
        tObject = tIter.next();
        if(condition to keep element)
            tKeepThese.add(tObject);
    }
    theCopyOnWriteArrayList.clear();
    theCopyOnWriteArrayList.addAll(tKeepThese);
    
    List tKeepThese=new ArrayList();
    for(ListIterator tIter=copyonWriteArrayList;tIter.hasNext();)
    {
    tObject=滴度。下一步();
    if(保留元素的条件)
    t保留这些。添加(tObject);
    }
    copyonWritearraylist.clear();
    copyonwritearraylist.addAll(保留这些内容);
    
    我本来打算提出这个建议,但我决定不这样做,因为这样会更冗长,效率更低。如果它是正确的,那么冗长就不应该是一个问题。你为什么说它效率更低?相反,在这种情况下,你会强制对“写时复制”集合进行一次更改“。你不这么认为吗?在这种情况下,它更有效,因为每个remove()操作都会创建一个临时数组,removeAll()只会创建一次。Vladimir。你是对的。虽然我希望找到一种方法在列表上迭代。这会更好-只有一个循环。和一个内存实例。将用于(int I=arr.size()-1;I>=0;I-)对你有用吗?尽管如此,临时数组是一个更好的解决方案。第三个也是我的幸运赢家。但是我如何迭代回去。列表没有提供以前的。我现在认为我的答案是一个糟糕的答案(请参阅刚才编辑的答案)。但是如果你真的想这样做,我想你必须使用旧样式的for循环,并从list.length()开始-1.都德。不要对自己太苛刻。至少你是第一个:-)。列表有一个列表迭代器,它有一个hasPrevious()和previous()方法。您可以像这样设置列表迭代器的初始位置:myList.listIterator(myList.size())。然后向后迭代。所以,这是可能的,但正如你所建议的,在这种情况下,这不是一个好主意。这很好,但会让我们计算list.size很多。但这是可能的。计算大小发生在移除元素的过程中,移除是真正的性能损失。如前所述,更好的做法是收集所有要删除的元素到一个临时数组中,并立即删除它们。