[英]map.containsKey(key) returns true, but map.get(key) doesn't return anything
[英]Why don't common Map implementations cache the result of Map.containsKey() for Map.get()
带有映射的常见模式是检查密钥是否存在,然后仅在其执行时对该值执行操作,请考虑:
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
}
因为缓存假设一个特定的用例,但实际上会减慢其他用户的速度。 它还增加了很多复杂性。
你如何缓存价值? 当多个线程一次读取时会发生什么?
坐下来开始思考所有可能发生的各种边缘情况和问题。 例如,如果在包含调用和get调用之间更改了值。 这种看似简单的变化实际上引入了很多复杂性并且减慢了许多操作,这些操作实际上比这个特定序列更频繁地使用。
您还应该考虑在非缓存之上构建“缓存映射”,但相反的情况是不可能的。
缓存在某些情况下是有帮助的,在其他情况下是有害的。 在基本映射实现中实现缓存会在缓存无用的情况下引起问题。
请记住,可以轻松地围绕非缓存映射构建一个包装器,该映射根据特定方案进行缓存。
我想这不值得:
get
情况下调用contains
,并且您的缓存会降低它的速度。 您可以使用始终正确的此代码段。
Value result = map.get(key);
if (result == null && !map.containsKey(key)) {
// handle absent key
}
除非key
不存在或映射为null
否则它仅使用单个操作。 我猜,在你的用例中,这种情况并不经常发生。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.