带有地图的常见模式是检查某个密钥是否存在,然后仅在该值处理时才采取行动,请考虑:
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
' s .containsKey()
看起来像这样:
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
和番石榴的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
}
答案 0 :(得分:6)
因为缓存假设一个特定的用例,但实际上会减慢其他用户的速度。它也增加了很多复杂性。
你如何缓存价值?当多个线程一次读取时会发生什么?
坐下来开始思考所有可能发生的各种边缘情况和问题。例如,如果在包含调用和get调用之间更改了值。这种看似简单的变化实际上引入了很多复杂性并且减慢了许多操作,这些操作实际上比这个特定序列更频繁地使用。
您还应该考虑在非缓存之上构建“缓存映射”,但相反的情况是不可能的。
答案 1 :(得分:2)
缓存在某些情况下是有帮助的,在其他情况下是有害的。在基本映射实现中实现缓存会在缓存无用的情况下引起问题。
请记住,可以轻松地围绕非缓存地图构建一个包装器,该地图可根据特定方案进行缓存。
答案 2 :(得分:2)
我认为这不值得:
contains
的情况下调用get
,并且您的缓存会降低速度。 您可以使用始终正确的此代码段。
Value result = map.get(key);
if (result == null && !map.containsKey(key)) {
// handle absent key
}
除非key
不存在或映射到null
,否则它只使用一个操作。我想,在你的使用案例中,这并不经常发生。
答案 3 :(得分:1)