【发布时间】:2014-06-19 08:26:24
【问题描述】:
使用映射的常见模式是检查键是否存在,然后仅在存在时才对值进行操作,请考虑:
if(!map.containsKey(key)) {
map.put(key, new DefaultValue());
}
return map.get(key);
但是这通常被认为很差,因为它需要两次地图查找,而这种替代方案只需要一次:
Value result = map.get(key);
if(result == null)
{
result = new DefaultValue();
map.put(key,result);
}
return result;
然而,第二个实现有它自己的问题。除了不太简洁和可读性之外,它还可能不正确,因为它无法区分密钥不存在和密钥存在但显式映射到null 的情况。当然,在个别情况下,我们可以创建映射不包含 null 值的外部不变量,但通常我们不能依赖第二种模式,需要退回到效率较低的实现。
但是为什么第一个实现需要降低效率呢? HashMap 的 .containsKey() 看起来像这样:
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
而Guava的ImmutableMap.containsKey()同样是:
public boolean containsKey(@Nullable Object key) {
return get(key) != null;
}
由于这些调用完成了.get() 的所有工作,缓存此调用的结果,然后短循环对同一密钥的连续调用.get() 有什么缺点?看起来成本是一个单一的指针,但好处意味着实现这种模式的“正确”方式也是这样做的“高效”方式。
private transient Entry<K,V> lastContainsKeyResult = null;
public boolean containsKey(Object key) {
lastContainsKeyResult = getEntry(key);
return lastContainsKeyResult != null;
}
public V get(Object key) {
if(key != null && lastContainsKeyResult != null &&
key.equals(lastContainsKeyResult.getKey()) {
return lastContainsKeyResult.getValue();
}
// normal hash lookup
}
【问题讨论】:
-
这是什么
DefaultValue? -
您必须同步
containsKey和get方法才能在多线程代码中正常工作。 -
您还需要添加脏标志以指示地图何时发生更改。
-
@SotiriosDelimanolis 只是一个虚拟对象,它不应该与问题有任何关系......
-
@Duncan 非常好,这将引起关注。
标签: java collections guava short-circuiting