繁体   English   中英

为什么常见的Map实现不会为Map.get()缓存Map.containsKey()的结果

[英]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否则它仅使用单个操作。 我猜,在你的用例中,这种情况并不经常发生。

其他答案涵盖了要点,但我想特别说明这一点:

第二个实现有它自己的问题。 除了不那么简洁和可读之外,它可能是不正确的,因为它无法区分密钥不存在的情况和密钥存在的情况,但显式地映射为null。

我从这个答案(在这个评论中)带走的东西如下: 你真的想要区分 null 和缺席值吗?

虽然我不能用一般性的说法,但我会说根据我的个人经验,我从来不需要将键明确地映射到null。

我推测,将null插入到地图中的设计主要用于表示发生了特殊/负面情况。 在这种情况下,我可能会考虑使用空对象模式,而不是存储一个实际对象,该对象通过其方法返回值向调用者指示已发生特殊情况。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM