在迭代时从java中删除集合中的项目
我希望能够从一组中删除多个元素,而我正在迭代它。 最初我希望迭代器足够聪明,以便下面的天真的解决scheme工作。
Set<SomeClass> set = new HashSet<SomeClass>(); fillSet(set); Iterator<SomeClass> it = set.iterator(); while (it.hasNext()) { set.removeAll(setOfElementsToRemove(it.next())); }
但是这会抛出一个ConcurrentModificationException
exception。
请注意,iterator.remove()将无法正常工作,因为我需要一次删除多个内容。 另外假定不能确定哪些元素要“即时”移除,但是可以写入方法setOfElementsToRemove()
。 在我的具体情况下,将需要大量的内存和处理时间来确定迭代时要删除的内容。 由于内存限制,制作副本也是不可能的。
setOfElementsToRemove()
将生成一些我想要移除的SomeClass实例集合, fillSet(set)
会用条目填充集合。
在search堆栈溢出后,我无法find这个问题的一个很好的解决scheme,但几个小时后休息我意识到以下将做的工作。
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
空。
我的问题是,是否有人有更好的方式来做这件事,或者是否有收集操作来支持这种清除。
此外,我想我会张贴我的解决scheme,因为似乎有需要,我想贡献堆栈溢出的优秀资源。
正常情况下,当您在集合中循环移除集合中的元素时,将会得到“ 并发修改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);
而不是迭代Set中的所有元素来删除你想要的元素,你可以实际使用Google Collections(不是你自己做不到的东西),而是应用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); } }
任何涉及从迭代中迭代的解决scheme,而不是通过迭代器,绝对不行。 除了可能的一个:你可以使用Collections.newSetFromMap(new ConcurrentHashMap<SomeClass, Boolean>( sizing params ))
。 现在的问题是你的迭代器只是弱一致的 ,这意味着每次你移除一个你还没有遇到的元素时,它的undefined是否会在你的迭代中显示出来。 如果这不是问题,这可能适合你。
你可以做的另一件事是build立一个toRemove
集,而不是你去,然后set.removeAll(itemsToRemove);
只在最后。 或者,在开始之前复制集合,以便在从另一个中移除的同时迭代一个副本。
编辑:哎呀,我看到toRemove
已经build议toRemove
想法(尽pipe与不必要的手滚removeAll
)。
您可以尝试java.util.concurrent.CopyOnWriteArraySet
,它为您提供了一个迭代器,该迭代器是迭代器创build时的集合的快照。 你对这个集合所做的任何修改(例如通过调用removeAll()
)在迭代器中都是不可见的,但是如果你看看集合本身(而removeAll()
不会抛出)。
有一个简单的答案 – 使用Iterator.remove()方法。
如果你有足够的内存来存放这个集合的一个副本,那么我假设你也有足够的内存来存放两个副本。 你引用的卡夫卡式的规则似乎并不禁止:)
那么我的build议是:
fillSet(set); fillSet(copy); for (Object item : copy) { if (set.contains(item)) { // ignore if not set.removeAll(setOfStuffToRemove()) } }
所以副本保持不变,只是提供了东西循环,而设置受到删除。 在此期间被删除的东西将被忽略。
你为什么不使用你想删除的对象的迭代器的删除方法 ?
引入迭代器主要是因为枚举器在枚举时不能处理删除操作。
你应该调用Iterator.remove
方法。
另外请注意,在大多数java.util
集合中,如果集合的内容已更改,则remove
方法将生成exception。 所以,如果代码是multithreading的使用时要特别小心,或者使用并发集合。
可以实现一个Set
,允许在迭代它的元素时将其删除。
我认为标准实现(HashSet,TreeSet等)不允许使用它,因为这意味着它们可以使用更高效的algorithm,但这并不难。
以下是一个使用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()
操作(但是问题中的示例不需要)。
从Java API复制:
List接口提供了一个特殊的迭代器,称为ListIterator,除了Iterator接口提供的正常操作之外,还允许元素插入和replace以及双向访问。 提供了一种方法来获取列表迭代器,该列表迭代器从列表中的指定位置开始。
我想我会指出,是一种特殊的迭代器的ListIterator是为了replace而构build的。