在迭代时更新PriorityQueue

时间:2011-08-18 10:25:24

标签: java android priority-queue

我需要根据ID更新PriorityQueue中的一些固定优先级元素。我认为这是一种非常常见的情况,这是一个示例代码段(Android 2.2):

for (Entry e : mEntries) {
    if (e.getId().equals(someId)) {
        e.setData(newData);
    }
}

然后我使Entry“不可变”(没有setter方法),以便创建一个新的Entry实例并由setData()返回。我将我的方法修改为:

for (Entry e : mEntries) {
    if (e.getId().equals(someId)) {
        Entry newEntry = e.setData(newData);
        mEntries.remove(e);
        mEntries.add(newEntry);
     }
}

代码似乎工作正常,但有人指出在迭代时修改队列是一个坏主意:它可能抛出一个ConcurrentModificationException,我需要将我想要删除的元素添加到ArrayList并删除它以后。他没有解释原因,对我来说这看起来很费劲,但我在互联网上找不到任何具体的解释。

This post类似,但优先级可以改变,这不是我的情况)

任何人都可以澄清我的代码有什么问题,我应该如何更改它 - 最重要的是 - 为什么?

谢谢, Rippel


PS:一些实施细节......

PriorityQueue<Entry> mEntries = new PriorityQueue<Entry>(1, Entry.EntryComparator());

使用:

public static class EntryComparator implements Comparator<Entry> {
    public int compare(Entry my, Entry their) {
        if (my.mPriority < their.mPriority) {
            return 1;
        }
        else if (my.mPriority > their.mPriority) {
            return -1;
        }
        return 0;
    }
}

4 个答案:

答案 0 :(得分:6)

此代码位于PriorityQueue的Java 6实现中:

private class Itr implements Iterator<E> {
  /**
   * The modCount value that the iterator believes that the backing
   * Queue should have.  If this expectation is violated, the iterator
   * has detected concurrent modification.
   */
  private int expectedModCount = modCount;

  public E next() {
    if(expectedModCount != modCount) {
      throw new ConcurrentModificationException();
    }


  }

}

现在,为什么这个代码在这里?如果查看Javadoc for ConcurrentModificationException,如果在迭代完成之前对底层集合进行修改,您将发现迭代器的行为是未定义的。因此,许多集合实现了这种modCount机制。

修复您的代码

您需要确保不要在循环中修改代码。如果您的代码是单线程的(如图所示),那么您可以按照同事的建议进行操作,然后将其复制到列表中以供日后使用。此外,记录了Iterator.remove()方法的使用以防止ConcurrentModificationExceptions。一个例子:

List<Entry> toAdd = new ArrayList<Entry>();
Iterator it = mEntries.iterator();
while(it.hasNext()) {
  Entry e = it.next();

  if(e.getId().equals(someId)) {
    Entry newEntry = e.setData(newData);
    it.remove();
    toAdd.add(newEntry);
  }
}
mEntries.addAll(toAdd);

答案 1 :(得分:0)

稍微好一点的实现

List<Entry> toAdd = new ArrayList<Entry>();
for (Iterator<Entry> it= mEntries.iterator();it.hasNext();) {
    Entry e = it.next();
    if (e.getId().equals(someId)) {
        Entry newEntry = e.setData(newData);
        it.remove();
        toAdd.add(newEntry);
     }
}
mEntries.addAll(toAdd);

这使用了删除迭代器和后续的批量添加

答案 2 :(得分:0)

PriorityQueue的Javadoc明确地说:

“请注意,此实现未同步。如果任何线程在结构上修改列表,则多个线程不应同时访问PriorityQueue实例。而是使用线程安全的 PriorityBlockingQueue 类。” / p>

这似乎是你的情况。

答案 3 :(得分:0)

您的代码中已经解释了什么 - 实现迭代器,它可以通过交叉修改一致地迭代集合是相当困难的任务。你需要指定如何处理被删除的项目(将通过迭代器看到吗?),添加的项目,修改的项目......即使你可以一致地执行它,它将是相当复杂和无效的实现 - 并且,大多数情况下,不是非常有用,因为用例“无需修改迭代”就更常见了。因此,java架构师选择在迭代时拒绝修改,并且Java collection API中的大多数集合都遵循这一点,如果检测到这样的修改,则抛出ConcurrentModificationException。

至于你的代码 - 对我来说,你不应该让项目不可变。不变性是件好事,但不应过度使用。如果您在这里使用的Entry对象是某种域对象,并且您确实希望它们是不可变的 - 您可以创建某种临时数据持有者(MutableEntry)对象,在算法中使用它,并将数据复制到Entry之前返回。从我的角度来看,这将是最好的解决方案。