今天在发现错误时,我发现TreeMap迭代器行为在删除对象时有点奇怪。事实上,我在一个简单的例子中测试了不同的用途:
TreeMap<String, String> map = new TreeMap<String, String>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "4");
map.put("5", "5");
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println("Before "+entry.getKey());
iterator.remove();
System.out.println("After " +entry.getKey());
}
结果是:
Before 1
After 1
Before 2
After 2
Before 3
After 3
Before 4
After 4
Before 5
After 5
但如果我把它改为:
TreeMap<String, String> map = new TreeMap<String, String>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "4");
map.put("5", "5");
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
String key = "4";
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
if(entry.getKey().equals(key)){
iterator.remove();
System.out.println(entry.getKey());
}
}
然后,对于key = 4,结果为5,对于key = 5,由于链接在删除时被更改,结果为5。但为什么这些行为会有所不同。 JIT?即使这是答案,它们不应该是统一的吗?
答案 0 :(得分:6)
请参阅getValue()
的{{3}}的Javadoc:
返回与此条目对应的值。如果 映射已从支持映射中删除(由迭代器映射) 删除操作),此调用的结果未定义。
删除条目后,行为未定义,因此这可以解释您获得不同结果的原因。
注意:另一位用户aix发布了类似的答案,我将对此发表评论,但由于某种原因,它已被删除/删除。
编辑:我不确定这个答案是否正确,因为我刚注意到OP正在调用getKey()
而不是getValue()
。
编辑2:感谢Ed Staub:我认为一般的想法仍然存在,因为Map.Entry
状态的Javadoc:
...更正式地说,如果在迭代器返回条目后修改了支持映射,则映射条目的行为是未定义的,除非通过映射条目上的setValue操作。
答案 1 :(得分:2)
要回答关于为什么会发生这种情况的问题,评论会给出一个提示
// If strictly internal, copy successor's element to p and then make p
// point to successor.
AFAIK,这是保持树平衡的一部分。
这意味着如果您只有一个子节点(或没有)节点本身被删除。如果节点有两个子节点,则该节点将替换为其后继节点。
如果您始终从一开始就删除该节点,您仍然可以使用该节点,但不会对其进行修改。如果从树的中间删除右节点,则会修改该条目以平衡树,因此在删除后使用该条目可以看到修改后的条目。
答案 2 :(得分:0)
我个人不会称这是一种“奇怪”的行为。它可能不是你所期望的。
TreeMap
在内部实现red-black tree。删除节点时,树得到了重新平衡(参见TreeMap.deleteEntry(Entry<K,V> p)
),并且有几种情况如何完成这种重新绑定,这取决于当前树分别删除哪个节点。