【问题标题】:Removing items from a collection in java while iterating over it在迭代它时从java中的集合中删除项目
【发布时间】:2010-12-13 02:24:57
【问题描述】:

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

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 的优秀资源。

【问题讨论】:

  • 如何使用'next'来确定要删除的元素?它可以帮助提供更好的答案。
  • 从这个问题和下面的答案中可以学到很多东西。

标签: java collections set


【解决方案1】:

通常,当您在遍历集合时从集合中删除元素时,您会得到一个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
  • 看起来不错,但我会用 set.removeAll(removalSet) 替换第二个示例中的最后一个循环。
  • @rob 是的,当它被指出时很明显。下次我会更好地校对我的代码。
【解决方案2】:

您实际上可以使用 Google 集合(虽然不是您自己无法做到的)并将谓词应用于 mask 那些你不需要的。

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

【讨论】:

  • A+ 表示努力和质量 :) +1
  • 可以使用Sets.difference()进行简化
【解决方案3】:

任何涉及在迭代时从正在迭代的集合中删除但不是通过迭代器的解决方案绝对行不通。除了可能的一个:您可以使用Collections.newSetFromMap(new ConcurrentHashMap&lt;SomeClass, Boolean&gt;(<em>sizing params</em>))。问题是现在你的迭代器只是弱一致,这意味着每次你删除一个你还没有遇到的元素时,不确定该元素是否会在你的迭代后期出现。如果这不是问题,这可能对您有用。

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

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 在这种情况下不起作用。它只删除迭代器返回的当前元素,我需要一次删除多个元素。
      • 然后只需对要删除的每个元素调用 remove。
      • 除非您想根据条件删除一堆元素(即在找到重复元素时删除两个元素),否则这是可行的方法。否则,请使用彼得添加的内容。
      • 因为我需要能够在迭代集合期间的任何时候从集合中删除任意元素。在我的具体情况下,没有简单的方法可以“即时”知道是否应该删除当前元素。抱歉,我应该说清楚,并且会。
      【解决方案6】:

      如果您有足够的内存来存储该集合的一个副本,我假设您也有足够的内存来存储两个副本。您引用的卡夫卡式规则似乎并没有禁止:)

      那么我的建议:

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

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

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

          你应该调用Iterator.remove方法。

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

          【讨论】:

            【解决方案9】:

            可以实现一个Set,允许在对其进行迭代时删除其元素。

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

            这是一个使用 Google 收藏的不完整示例:

            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()操作(但问题中的例子不需要)

            【讨论】:

              【解决方案10】:

              复制自Java API:

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

              我想我会指出 ListIterator 是一种特殊的 Iterator 是为替换而构建的。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2011-03-03
                • 2017-05-15
                • 2015-03-13
                • 2012-05-13
                相关资源
                最近更新 更多