从CopyOnWriteArrayList中删除元素

当我尝试使用迭代器从CopyOnWriteArrayList中删除元素时,我收到exception。 我注意到它已被记录在案

不支持对迭代器本身进行元素更改操作(删除,设置和添加)。 这些方法抛出UnsupportedOperationException。

(来自http://download.oracle.com/javase/6/docs/api/java/util/concurrent/CopyOnWriteArrayList.html )

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

迭代集合,选择要删除的所有元素并将其放入临时集合中。 完成迭代后,使用removeAll方法从原始集合中删除所有找到的元素。

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

编辑:我是个白痴。 我错过了这是一个写入时复制列表的事实,因此每次删除都意味着一个新的副本 。 因此,如果有多个删除,我下面的建议可能不是最理想的。

与迭代器不支持remove的任何其他列表相同,或者您没有使用迭代器的任何其他列表。 为避免此错误,有三种基本技术可供考虑:

  1. 删除某些内容后递减索引(注意在下一次迭代之前不要对索引执行任何操作)。 为此,你显然必须使用for(int i=0; i < ... for循环的样式,以便你可以操作索引。

  2. 以某种方式重复循环内部正在做的事情,而不是回到循环的顶部。 一点点黑客 - 我会避免这种技术。

  3. 反向迭代列表(从头到尾,而不是从头到尾)。 我更喜欢这种方法,因为它是最简单的方法。

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

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

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

通常你会先迭代收集elemenet,然后在一个单独的列表中删除,然后在每个循环之外删除它们(无论如何都是伪装的基于迭代器的循环)

像这样的东西:

 int pos = 0; while(pos < lst.size() ) { Foo foo = lst.get(pos); if( hasToBeRemoved(foo) ) { lst.remove(pos); // do not move position } else { pos++; } } 

您可以使用Queue而不是List。

 private Queue queue = new ConcurrentLinkedQueue(); 

它是线程安全的,并支持iterator.remove() 。 但请注意Queue迭代器的线程安全行为(检查javadoc)。

最短最有效的方式:

 List list = new CopyOnWriteArrayList<>(); list.removeIf(s -> s.length() < 1); 

在内部,它创建一个具有相同长度的临时数组,并复制谓词返回true的所有元素。

请记住,如果您使用此方法实际迭代元素以执行某些操作,则由于removeIf调用是primefaces的并且将锁定其他线程的遍历,因此无法再在并行中执行这些操作

Interesting Posts