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

nas*_*ash 30 java collections set

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

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

但这引发了一场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));
}
Run Code Online (Sandbox Code Playgroud)

setOfElementsToRemoveIncludingThePassedValue()将生成一组要删除的元素,包括传递给它的值.我们需要删除传递的值,因此set将为空.

我的问题是,是否有人有更好的方法这样做,或者是否有支持这种删除的收集操作.

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

Pet*_*ter 40

通常,当您在循环集合时从集合中删除元素时,您将获得并发修改异常.这部分是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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这样您就可以安全地从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);
Run Code Online (Sandbox Code Playgroud)


qno*_*oid 9

您可以实际使用Google集合(而不是您自己无法做到的事情)而不是遍历集合中的所有元素来删除所需的元素,并应用谓词来掩盖您不需要的集合..

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);
}    
}
Run Code Online (Sandbox Code Playgroud)


Kev*_*ion 6

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

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

编辑:哎呀,我看到彼得尼克斯已经提出了toRemove这个想法(虽然有一个不必要的手工卷removeAll).


joe*_*e p 6

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