迭代时从 HashSet 中删除元素 [重复]

Posted

技术标签:

【中文标题】迭代时从 HashSet 中删除元素 [重复]【英文标题】:Remove Elements from a HashSet while Iterating [duplicate] 【发布时间】:2010-11-09 18:31:08 【问题描述】:

所以,如果我在迭代时尝试从 Java HashSet 中删除元素,我会得到 ConcurrentModificationException。如下例所示,从 HashSet 中删除元素子集的最佳方法是什么?

Set<Integer> set = new HashSet<Integer>();

for(int i = 0; i < 10; i++)
    set.add(i);

// Throws ConcurrentModificationException
for(Integer element : set)
    if(element % 2 == 0)
        set.remove(element);

这是一个解决方案,但我认为它不是很优雅:

Set<Integer> set = new HashSet<Integer>();
Collection<Integer> removeCandidates = new LinkedList<Integer>();

for(int i = 0; i < 10; i++)
    set.add(i);

for(Integer element : set)
    if(element % 2 == 0)
        removeCandidates.add(element);

set.removeAll(removeCandidates);

谢谢!

【问题讨论】:

【参考方案1】:

您可以手动迭代集合的元素:

Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) 
    Integer element = iterator.next();
    if (element % 2 == 0) 
        iterator.remove();
    

您会经常看到这种模式使用for 循环而不是while 循环:

for (Iterator<Integer> i = set.iterator(); i.hasNext();) 
    Integer element = i.next();
    if (element % 2 == 0) 
        i.remove();
    

正如人们所指出的,使用for 循环是首选,因为它将迭代器变量(在本例中为i)限制在较小的范围内。

【讨论】:

我更喜欢for 而不是while,但每个人都有他/她自己的。 我自己也使用for。我使用while 希望使示例更清晰。 我更喜欢for,主要是因为迭代器变量被限制在循环的范围内。 如果使用while,则迭代器的作用域比它需要的大。 我更喜欢 while,因为它对我来说看起来更干净。如果您正在分解代码,迭代器的范围应该不是问题。有关分解代码的更多信息,请参阅 Becks 的书“测试驱动开发”或 Fowler 的“重构”。【参考方案2】:

您还可以重构您的解决方案,删除第一个循环:

Set<Integer> set = new HashSet<Integer>();
Collection<Integer> removeCandidates = new LinkedList<Integer>(set);

for(Integer element : set)
   if(element % 2 == 0)
       removeCandidates.add(element);

set.removeAll(removeCandidates);

【讨论】:

我不推荐这样做,因为它引入了隐藏的时间耦合。 @RomainF。 - 隐藏的时间耦合是什么意思?你的意思是线程安全的吗?其次,我也不建议这样做,但该解决方案确实有其优点。超级容易阅读,因此易于维护。 是的,for循环会产生副作用,但我同意它可能是最易读的解决方案,除非您使用的是Java 8。否则,只需使用“removeIf”方法。 我认为这个答案忽略了第一个循环只有一个 HashSet 可以从中删除某些元素。【参考方案3】:

这是更现代的流方法:

myIntegerSet.stream().filter((it) -> it % 2 != 0).collect(Collectors.toSet())

但是,这会产生一个新集合,因此如果它是一个非常大的集合,内存限制可能会成为一个问题。

编辑:此答案的先前版本建议使用 Apache CollectionUtils,但那是在蒸汽出现之前。

【讨论】:

这个答案真的开始显示它的年龄......现在有一种 Java-8 方法可以做到这一点,可以说是更干净。 有没有更好的方法可以使用,或者您只是指使用 lambda 代替匿名内部类的能力? 这是更现代的方式:myIntegerSet.stream().filter((it) -&gt; it % 2 != 0).collect(Collectors.toSet())【参考方案4】:

您获得ConcurrentModificationException 的原因是通过Set.remove() 而非Iterator.remove() 删除条目。如果在完成迭代时通过 Set.remove() 删除条目,您将收到 ConcurrentModificationException。另一方面,在这种情况下支持迭代时通过 Iterator.remove() 删除条目。

新的 for 循环很不错,但不幸的是它在这种情况下不起作用,因为您不能使用迭代器引用。

如果您需要在迭代时删除条目,则需要使用直接使用迭代器的长格式。

for (Iterator<Integer> it = set.iterator(); it.hasNext();) 
    Integer element = it.next();
    if (element % 2 == 0) 
        it.remove();
    

【讨论】:

@你的代码不应该调用 it.next() 吗? 谢谢。固定。 “元素”在什么时候被实例化? 呃。固定的。谢谢。【参考方案5】:

另一种可能的解决方案:

for(Object it : set.toArray())  /* Create a copy */
    Integer element = (Integer)it;
    if(element % 2 == 0)
        set.remove(element);

或者:

Integer[] copy = new Integer[set.size()];
set.toArray(copy);

for(Integer element : copy) 
    if(element % 2 == 0)
        set.remove(element);

【讨论】:

如果您碰巧在循环期间不仅删除了现有元素,而且还向集合中添加了新元素,那么(或从集合中创建一个ArrayList)是最好的解决方案。【参考方案6】:

Java 8 Collection 有一个名为 removeIf 的好方法,它使事情变得更容易和更安全。来自 API 文档:

default boolean removeIf(Predicate<? super E> filter)
Removes all of the elements of this collection that satisfy the given predicate. 
Errors or runtime exceptions thrown during iteration or by the predicate 
are relayed to the caller.

有趣的笔记:

The default implementation traverses all elements of the collection using its iterator(). 
Each matching element is removed using Iterator.remove().

来自: https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#removeIf-java.util.function.Predicate-

【讨论】:

一个例子:integerSet.removeIf(integer-&gt; integer.equals(5));【参考方案7】:

就像木头说的那样——“Java 8 Collection 有一个很好的方法叫做 removeIf,它可以让事情变得更简单、更安全”

这是解决您问题的代码:

set.removeIf((Integer element) -> 
    return (element % 2 == 0);
);

现在你的集合只包含奇数。

【讨论】:

以上是关于迭代时从 HashSet 中删除元素 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

迭代时从集合中删除元素

Python:如何在迭代列表时从列表中删除元素而不跳过未来的迭代

在迭代时从地图(或任何其他STL容器)中删除/删除内容

如何在迭代字典时从字典中删除项目?

如何去除List集合中重复的元素

HashSet源码解析&Map迭代器