11

情况:我有一个自定义对象的 TreeSet,并且我还使用了一个自定义比较器。我创建了一个迭代器以在此 TreeSet 上使用。

TreeSet<Custom> ts=new TreeSet<Custom>();
Iterator<Custom> itr=ts.iterator();
while(itr.hasNext()){
    Custom c=itr.next();
    //Code to add a new element to the TreeSet ts
}

问题:嗯,我想知道,如果我在 while 循环中向 TreeSet 添加一个新元素,那么该新元素会立即排序。换句话说,如果我在 while 循环中添加一个新元素并且它小于我当前在 c 中保存的元素,那么在下一次迭代中,我会在 c 中获得与上一次迭代相同的元素吗?(因为排序后,新添加的元素会占据当前元素之前的某个位置)。

4

7 回答 7

24

如果您在迭代期间添加一个元素,您的下一次迭代器调用可能会抛出一个ConcurrentModificationException. 请参阅TreeSet文档中的快速失败行为。

要迭代和添加元素,您可以先复制到另一个集合:

TreeSet<Custom> ts = ...
TreeSet<Custom> tsWithExtra = new TreeSet(ts);

for (Custom c : ts) {
  // possibly add to tsWithExtra
}

// continue, using tsWithExtra

或者按照 Colin 的建议创建一个单独的集合以ts在迭代后合并。

于 2011-06-23T20:39:29.043 回答
6

如果在 while 循环中将元素添加到 TreeSet中,您将获得java.util.ConcurrentModificationException 。

Set<String> ts=new TreeSet<String>();
ts.addAll(Arrays.asList(new String[]{"abb", "abd", "abg"}));
Iterator<String> itr=ts.iterator();
while(itr.hasNext()){
    String s = itr.next();
    System.out.println("s: " + s);
    if (s.equals("abd"))
        ts.add("abc");
}

输出

Exception in thread "main" java.util.ConcurrentModificationException
于 2011-06-23T20:43:48.267 回答
3
public static void main(String[] args) {
    TreeSet<Integer> ts=new TreeSet<Integer>();
    ts.add(2);
    ts.add(4);
    ts.add(0);

    Iterator<Integer> itr=ts.iterator();
    while(itr.hasNext()){
        Integer c=itr.next();
        System.out.println(c);
        //Code
        ts.add(1);
    }
}


Exception in thread "main" java.util.ConcurrentModificationException

这将适用于所有集合,例如,,List因为 当迭代器启动时,它可能会对其进行锁定。MapSet

如果您使用迭代器迭代列表,则会出现此异常。我认为否则这个循环将是无限的,因为你正在添加元素整体迭代。

考虑没有迭代器:

public static void main(String[] args) {
    List<Integer> list=new ArrayList<Integer>();
    list.add(2);
    list.add(4);
    list.add(0);

    for (int i = 0; i < 3; i++) {
        System.out.println(list.get(i));
        list.add(3);
    }
    System.out.println("Size" +list.size());
}

这会没事的。

于 2011-06-23T21:02:07.477 回答
1

为了避免ConcurrentModificationException你可能想看看我的UpdateableTreeSet. 我什至添加了一个新的测试用例,展示了如何在循环中添加元素。更准确地说,您标记新元素以供以后延迟更新集合。这很好用。基本上你会做类似的事情

for (MyComparableElement element : myUpdateableTreeSet) {
    if (someCondition) {
        // Add new element (deferred)
        myUpdateableTreeSet.markForUpdate(
            new MyComparableElement("foo", "bar", 1, 2)
        );
    }
}

// Perform bulk update
myUpdateableTreeSet.updateMarked();

我想这正是你所需要的。:-)

于 2013-02-22T12:01:31.520 回答
0

防止行走时出现 ConcurrentModificationException。下面是我的版本,允许高频插入 TreeSet() 并允许同时对其进行迭代。当 TreeSet 被迭代时,此类使用一个额外的队列来存储插入对象。

public class UpdatableTransactionSet {
TreeSet <DepKey> transactions = new TreeSet <DepKey> ();
LinkedList <DepKey> queue = new LinkedList <DepKey> ();
boolean busy=false;
/**
 * directly call it
 * @param e
 */
void add(DepKey e) {
    boolean bb = getLock();
    if(bb) {
        transactions.add(e);
        freeLock();
    } else {
        synchronized(queue) {
            queue.add(e);
        }
    }
}
/**
 * must getLock() and freeLock() while call this getIterator function
 * @return
 */
Iterator<DepKey> getIterator() {
    return null;
}

synchronized boolean getLock() {
    if(busy) return false;
    busy = true;
    return true;
}
synchronized void freeLock() {
    synchronized(queue) {
        for(DepKey e:queue) {
            transactions.add(e);
        }
    }       
    busy = false;
}
}
于 2014-03-18T07:58:51.057 回答
0

虽然问题已经得到解答,但我认为最令人满意的答案在于TreeSet本身的 javadoc

此类的迭代器方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时间修改集合,除了通过迭代器自己的 remove 方法之外,迭代器将抛出 ConcurrentModificationException。因此,面对并发修改,迭代器快速而干净地失败,而不是在未来不确定的时间冒任意的、非确定性的行为。

请注意,不能保证迭代器的快速失败行为,>一般来说,在存在不同步的并发修改的情况下不可能做出任何硬保证。快速失败的迭代器会尽最大努力抛出 ConcurrentModificationException。因此,编写一个依赖于这个异常的正确性的程序是错误的:迭代器的快速失败行为应该只用于检测错误。

于 2015-01-16T07:05:10.520 回答
0

为避免在进行插入时必然会发生并发修改错误,您还可以创建 Set 的临时副本,改为遍历副本,然后修改原始副本。

于 2017-04-14T19:11:48.010 回答