迭代时更新 PriorityQueue
Posted
技术标签:
【中文标题】迭代时更新 PriorityQueue【英文标题】:Updating a PriorityQueue when iterating it 【发布时间】:2011-08-18 10:25:24 【问题描述】:我需要根据它们的 ID 更新 PriorityQueue 中的一些固定优先级元素。我认为这是一个很常见的场景,这里有一个示例 sn-p (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 类似,但优先级可以改变,这不是我的情况)
谁能澄清我的代码有什么问题,我应该如何更改它以及 - 最重要的是 - 为什么?
谢谢, 涟漪
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;
【问题讨论】:
可能创建一个堆栈,然后在完成后将所需的元素添加到那里。 【参考方案1】:这段代码在 Java 6 的 PriorityQueue 实现中:
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);
【讨论】:
很好的解释和解决方案:)【参考方案2】:PriorityQueue 的 Javadoc 明确指出:
“请注意,此实现不是同步的。如果任何线程在结构上修改列表,则多个线程不应同时访问 PriorityQueue 实例。相反,请使用线程安全的 PriorityBlockingQueue 类。”
这似乎是你的情况。
【讨论】:
【参考方案3】:您的代码中的问题已经解释过了——实现迭代器,它可以通过交叉修改一致地迭代集合,这是一项相当艰巨的任务。您需要指定如何处理已删除的项目(将通过迭代器看到吗?),添加的项目,修改的项目......即使您可以始终如一地做到这一点,它也将是相当复杂和低效的实现——而且,大多数情况下,不是非常有用,因为用例“无需修改即可迭代”更为常见。因此,Java 架构师选择在迭代时拒绝修改,Java 集合 API 中的大多数集合都遵循这一点,如果检测到此类修改,则抛出 ConcurrentModificationException。
至于你的代码——对我来说,你不应该让项目不可变。不变性是件好事,但不应过度使用。如果您在此处使用的 Entry 对象是某种领域对象,并且您真的希望它们是不可变的——您可以创建某种临时数据持有者 (MutableEntry) 对象,在您的算法中使用它,然后将数据复制到 Entry返回。从我的角度来看,这将是最好的解决方案。
【讨论】:
【参考方案4】:一个稍微好一点的实现是
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);
这使用了迭代器的删除和之后的批量添加
【讨论】:
以上是关于迭代时更新 PriorityQueue的主要内容,如果未能解决你的问题,请参考以下文章
PyQt5如何使用python在迭代循环中更新单元格值Qtablewidget