在java中迭代时从集合中移除项

cgvd09ve  于 2021-06-30  发布在  Java
关注(0)|答案(10)|浏览(424)

我希望在迭代时能够从一个集合中删除多个元素。起初,我希望迭代器足够聪明,下面的天真解决方案可以工作。

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) 将用条目填充集合。
搜索堆栈溢出后,我找不到解决这个问题的好方法,但几个小时后,我意识到下面的方法可以解决这个问题。

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` 将清空。
我的问题是,是否有人有更好的方法来实现这一点,或者是否有支持此类删除的收集操作。
另外,我想我会张贴我的解决方案,因为似乎有需要,我想贡献优秀的资源,即堆栈溢出。
h22fl7wq

h22fl7wq1#

任何涉及在迭代过程中从正在迭代的集合中移除的解决方案(而不是通过迭代器)都绝对不起作用。可能除了一个:你可以用一个 Collections.newSetFromMap(new ConcurrentHashMap<SomeClass, Boolean>(sizing params)) . 问题是现在你的迭代器只是弱一致的,这意味着每次你删除一个你还没有遇到的元素时,这个元素是否会在你的迭代中出现是不确定的。如果这不是问题,这可能对你有用。
你可以做的另一件事是建立一个 toRemove 那就随你去吧 set.removeAll(itemsToRemove); 只有在最后。或者,在开始之前复制集合,这样就可以迭代一个副本,同时从另一个副本中删除。
编辑:哎呀,我知道彼得尼克斯已经建议 toRemove 想法(尽管有一个不必要的手卷 removeAll ).

7ivaypg9

7ivaypg92#

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

omjgkv6w

omjgkv6w3#

你可以使用googlecollections(不是你自己做不到的)并应用一个 predicate 来屏蔽你不需要的元素,而不是遍历集合中的所有元素来删除你想要的元素。

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);
}    
}
nnvyjq4y

nnvyjq4y4#

如果你有足够的内存来存储一个副本,我假设你也有足够的内存来存储两个副本。你引用的Kafka式的规则似乎并不禁止:)
那么,我的建议是:

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

所以copy保持不变,只提供循环的内容,而set会被删除。在此期间从集合中删除的内容将被忽略。

camsedfj

camsedfj5#

为什么不对要删除的对象使用迭代器的remove方法呢?
迭代器的引入主要是因为枚举器在枚举时不能处理删除操作。

vdzxcuhz

vdzxcuhz6#

有可能实现 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() 操作(但问题中的示例不需要它。)

ix0qys7i

ix0qys7i7#

你应该打电话 Iterator.remove 方法。
另请注意,在大多数情况下 java.util 收藏 remove 方法将在集合的内容发生更改时生成异常。因此,如果代码是多线程的,请格外小心,或者使用并发集合。

wz1wpwve

wz1wpwve8#

通常,当在集合上循环时从集合中移除元素时,会出现并发修改异常。这就是迭代器接口具有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);
zbsbpyhn

zbsbpyhn9#

你可以试试 java.util.concurrent.CopyOnWriteArraySet 它提供了一个迭代器,它是迭代器创建时集合的快照。对集合所做的任何更改(即通过调用 removeAll() )在迭代器中不可见,但是如果您查看集合本身(以及 removeAll() 不会扔)。

oo7oh9g9

oo7oh9g910#

从java api复制:
list接口提供了一个特殊的迭代器,称为listierator,除了迭代器接口提供的正常操作之外,它还允许元素插入和替换以及双向访问。提供了一种获取从列表中指定位置开始的列表迭代器的方法。
我想我应该指出listiterator是一种特殊的迭代器,它是为替换而构建的。

相关问题