在迭代它时从java中的集合中删除项目

时间:2009-11-04 16:37:00

标签: java collections set

我想在迭代它时从一个集合中删除多个元素。最初,我希望迭代器足够智能,以便下面的天真解决方案能够正常工作。

Set<SomeClass> set = new HashSet<SomeClass>();
fillSet(set);
Iterator<SomeClass> it = set.iterator();
while (it.hasNext()) {
    set.removeAll(setOfElementsToRemove(it.next()));
}

但这会引发ConcurrentModificationException

请注意,iterator.remove()将无法正常工作,因为我需要一次删除多个东西。还假设无法识别要“动态”删除哪些元素,但可以编写方法setOfElementsToRemove()。在我的特定情况下,它将占用大量内存和处理时间来确定迭代时要删除的内容。由于内存限制,也无法进行复制。

setOfElementsToRemove()将生成一些我想删除的SomeClass实例集,fillSet(set)将用条目填充集合。

在搜索Stack Overflow之后,我找不到解决这个问题的好方法,但是几个小时后我才意识到以下情况可以解决这个问题。

Set<SomeClass> set = new HashSet<SomeClass>();
Set<SomeClass> outputSet = new HashSet<SomeClass>();
fillSet(set);
while (!set.isEmpty()) {
    Iterator<SomeClass> it = set.iterator();
    SomeClass instance = it.next();
    outputSet.add(instance);
    set.removeAll(setOfElementsToRemoveIncludingThePassedValue(instance));
}

setOfElementsToRemoveIncludingThePassedValue()将生成一组要删除的元素,包括传递给它的值。我们需要删除传递的值,以便set清空。

我的问题是,是否有人有更好的方法来执行此操作,或者是否有支持这种删除的收集操作。

另外,我想我会发布我的解决方案,因为似乎有需要,我想贡献Stack Overflow的优秀资源。

10 个答案:

答案 0 :(得分:40)

通常,当您在循环集合时从集合中删除元素时,您将获得Concurrent Modification Exception。这部分是Iterator接口具有remove()方法的部分原因。使用迭代器是在遍历它们时修改元素集合的唯一安全方法。

代码会是这样的:

Set<SomeClass> set = new HashSet<SomeClass>();
fillSet(set);
Iterator<SomeClass> setIterator = set.iterator();
while (setIterator.hasNext()) {
    SomeClass currentElement = setIterator.next();
    if (setOfElementsToRemove(currentElement).size() > 0) {
        setIterator.remove();
    }
}

这样您就可以安全地从setOfElementsToRemove()中删除所有生成删除集的元素。

修改

根据对其他答案的评论,这可能更符合您的要求:

Set<SomeClass> set = new HashSet<SomeClass>();
Set<SomeClass> removalSet = new HashSet<SomeClass>();
fillSet(set);

for (SomeClass currentElement : set) {
    removalSet.addAll(setOfElementsToRemove(currentElement);
}

set.removeAll(removalSet);

答案 1 :(得分:9)

不是迭代Set中的所有元素来删除你想要的那些元素,而是实际上可以使用Google Collections(不是你不能自己做的事情)并将谓词应用于 mask < / em>你不需要的那些。

package com.stackoverflow.q1675037;

import java.util.HashSet;
import java.util.Set;

import org.junit.Assert;
import org.junit.Test;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;


public class SetTest
{
public void testFilter(final Set<String> original, final Set<String> toRemove, final Set<String> expected)
{

    Iterable<String> mask = Iterables.filter(original, new Predicate<String>()
    {
        @Override
        public boolean apply(String next) {
        return !toRemove.contains(next);
        }
    });

    HashSet<String> filtered = Sets.newHashSet(mask);

    Assert.assertEquals(original.size() - toRemove.size(), filtered.size());
    Assert.assertEquals(expected, filtered);        
}


@Test
public void testFilterNone()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> toRemove = new HashSet();

    Set<String> expected = new HashSet<String>(){
        {
            this.add("foo");                
            this.add("bar");
            this.add("foobar");
        }
    };

    this.testFilter(original, toRemove, expected);
}

@Test
public void testFilterAll()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> toRemove = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    HashSet<String> expected = new HashSet<String>();
    this.testFilter(original, toRemove, expected);
}    

@Test
public void testFilterOne()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> toRemove = new HashSet<String>(){
        {
            this.add("foo");
        }
    };

    Set<String> expected = new HashSet<String>(){
        {
            this.add("bar");
            this.add("foobar");
        }
    };

    this.testFilter(original, toRemove, expected);
}    


@Test
public void testFilterSome()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

   Set<String> toRemove = new HashSet<String>(){
        {
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> expected = new HashSet<String>(){
        {
            this.add("foo");
        }
    };

    this.testFilter(original, toRemove, expected);
}    
}

答案 2 :(得分:6)

任何涉及从迭代中删除迭代的解决方案而不是通过迭代器的解决方案都绝对不起作用。除了可能的一个:你可以使用Collections.newSetFromMap(new ConcurrentHashMap<SomeClass, Boolean>(sizing params))。问题是,现在你的迭代器只是弱一致,这意味着每次你删除一个你还没有遇到的元素时,它是否未定义该元素是否会在你的迭代中出现。如果这不是问题,这可能适合你。

您可以做的另一件事是建立一个toRemove集合,然后只在最后建立set.removeAll(itemsToRemove);。或者,在开始之前复制该集,这样您就可以在从另一个副本移除时迭代一个副本。

编辑:oops,我看到Peter Nix已经提出了toRemove的想法(尽管有一个不必要的手卷removeAll)。

答案 3 :(得分:6)

您可以尝试java.util.concurrent.CopyOnWriteArraySet,它会为您提供一个迭代器,它是迭代器创建时集的快照。您对集合所做的任何更改(即通过调用removeAll())都不会在迭代器中可见,但如果您查看集合本身(并且removeAll()将不会抛出),则可见。< / p>

答案 4 :(得分:2)

对此有一个简单的答案 - 使用Iterator.remove()方法。

答案 5 :(得分:2)

如果你有足够的内存用于该套装的一个副本,我会假设你有两个副本的足够内存。您引用的Kafka-esque规则似乎并不禁止:)

我的建议是:

fillSet(set);
fillSet(copy);
for (Object item : copy) {
   if (set.contains(item)) { // ignore if not
     set.removeAll(setOfStuffToRemove())
   }
}

因此副本保持不变,只是提供循环的东西,而set则遭受删除。在此期间从集合中删除的东西将被忽略。

答案 6 :(得分:1)

为什么不在要删除的对象上使用iterator's remove method

引入迭代器主要是因为枚举器在枚举时无法处理删除。

答案 7 :(得分:0)

您应该调用Iterator.remove方法。

另请注意,在大多数java.util个集合中,如果集合的内容发生更改,remove方法将生成异常。因此,如果代码是多线程的,请格外小心,或使用并发集合。

答案 8 :(得分:0)

可以实现一个Set,允许在迭代它时删除它的元素。

我认为标准实现(HashSet,TreeSet等)不允许它,因为这意味着它们可以使用更高效的算法,但这并不难。

以下是使用Google Collections的不完整示例:

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.google.common.base.Predicates;
import com.google.common.collect.ForwardingSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;

public class ConcurrentlyModifiableSet<E>
extends ForwardingSet<E> {
 /** Create a new, empty set */
 public ConcurrentlyModifiableSet() {
  Map<E, Boolean> map = new ConcurrentHashMap<E, Boolean>();
  delegate = Sets.newSetFromMap(map);
 }

 @Override
 public Iterator<E> iterator() {
  return Iterators.filter(delegate.iterator(), Predicates.in(delegate));
 }

 @Override
 protected Set<E> delegate() {
  return this.delegate;
 }

 private Set<E> delegate;
}

注意:迭代器不支持remove()操作(但问题中的示例不需要它。)

答案 9 :(得分:0)

Java API

复制
  

List接口提供了一个特殊的迭代器,称为ListIterator,   除了Iterator的正常操作外,允许元素插入和替换,和双向访问   界面提供。提供了一种获取列表迭代器的方法   从列表中的指定位置开始。

我想我会指出,ListIterator是一种特殊的迭代器,是为了替换而构建的。