使用map.get()时使用Java Map.containsKey()冗余


90

我一直想知道,在最佳实践中,是否允许不使用containsKey()on方法java.util.Map,而是对from的结果进行空检查get()

我的理由是,对值进行两次查找似乎是多余的:首先对进行查找,containsKey()然后对进行查找get()

另一方面,可能是大多数标准实现都Map缓存了最后的查找,或者编译器可以通过其他方式消除冗余,并且对于代码的可读性而言,最好保留该containsKey()部分。

非常感谢您的评论。

Answers:


112

某些Map实现被允许具有空值,例如HashMap,在这种情况下,如果get(key)返回null,则不能保证地图中没有与此键关联的条目。

因此,如果您想知道地图是否包含键use Map.containsKey。如果仅需要映射到键的值,请使用Map.get(key)。如果此映射允许空值,则返回值null不一定表示该映射不包含键的映射。在这种情况下Map.containsKey是无用的,会影响性能。此外,在并发访问地图的情况下(例如ConcurrentHashMap),在测试之后,您Map.containsKey(key)有可能在调用之前由另一个线程删除该条目Map.get(key)


8
即使将值设置为null,您是否也要对未设置的键/值区别对待?如果您不需要特别对待,可以使用get()
Peter Lawrey 2013年

1
如果您Mapprivate,则您的课程可能能够保证a null不会插入到地图中。在这种情况下,您可以使用,get()然后再检查是否为null而不是containsKey()。在某些情况下,这样做可能会更清晰,甚至更有效率。
2013年

44

我认为写是相当标准的:

Object value = map.get(key);
if (value != null) {
    //do something with value
}

代替

if (map.containsKey(key)) {
    Object value = map.get(key);
    //do something with value
}

它的可读性也不低,效率也更高,因此我看不到任何不这样做的理由。显然,如果您的地图可以包含null,则这两个选项的语义不相同


8

正如sysylias指出的,这是一个语义问题。通常,您想要的是Map.get(x)== null,但是在某些情况下,使用containsKey很重要。

一种这样的情况是高速缓存。我曾经在一个Web应用程序中处理过一个性能问题,该应用程序经常查询其数据库以查找不存在的实体。研究该组件的缓存代码时,我意识到如果cache.get(key)== null,它正在查询数据库。如果数据库返回空值(找不到实体),我们将缓存该键->空值映射。

切换到containsKey解决了该问题,因为映射到null值实际上意味着某些事情。映射到null的键具有与不存在的键不同的语义。


有趣。为什么不在缓存值之前简单地添加一个空检查?
Saket 2015年

那不会改变任何东西。问题的关键是,该键映射到null意味着“我们已经做到了这一点。它的缓存。该值零”。与根本不包含给定密钥相比,这意味着“不知道,不在缓存中,我们可能需要检查数据库”。
布兰登

4
  • containsKeyget仅当我们先验地知道绝不会允许使用null值时,后接a 才是多余的。如果null值无效,则的调用containsKey会带来不小的性能损失,并且只是开销,如下面的基准所示。

  • 与仅空值检查相比,Java 8 Optional惯用语- Optional.ofNullable(map.get(key)).ifPresentOptional.ofNullable(map.get(key)).ifPresent-产生了不小的开销。

  • A HashMap使用O(1)常量表查找,而A TreeMap使用O(log(n))查找。在上调用时,containsKey后面跟get成语要慢得多TreeMap

基准测试

参见https://github.com/vkarun/enum-reverse-lookup-table-jmh

// t1
static Type lookupTreeMapNotContainsKeyThrowGet(int t) {
  if (!lookupT.containsKey(t))
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return lookupT.get(t);
}
// t2
static Type lookupTreeMapGetThrowIfNull(int t) {
  Type type = lookupT.get(t);
  if (type == null)
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return type;
}
// t3
static Type lookupTreeMapGetOptionalOrElseThrow(int t) {
  return Optional.ofNullable(lookupT.get(t)).orElseThrow(() -> new 
      IllegalStateException("Unknown Multihash type: " + t));
}
// h1
static Type lookupHashMapNotContainsKeyThrowGet(int t) {
  if (!lookupH.containsKey(t))
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return lookupH.get(t);
}
// h2
static Type lookupHashMapGetThrowIfNull(int t) {
  Type type = lookupH.get(t);
  if (type == null)
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return type;
}
// h3
static Type lookupHashMapGetOptionalOrElseThrow(int t) {
  return Optional.ofNullable(lookupH.get(t)).orElseThrow(() -> new 
    IllegalStateException("Unknown Multihash type: " + t));
}
基准(迭代)(lookupApproach)模式Cnt得分错误单位

MultihashTypeLookupBenchmark.testLookup 1000 t1平均9 33.438±4.514 us / op
MultihashTypeLookupBenchmark.testLookup 1000 t2平均9 26.986±0.405 us / op
MultihashTypeLookupBenchmark.testLookup 1000 t3平均9 39.259±1.306 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h1平均9​​ 18.954±0.414 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h2平均9 15.486±0.395 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h3平均9 16.780±0.719 us / op

TreeMap来源参考

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/TreeMap.java

HashMap来源参考

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/HashMap.java


3

我们可以使用Java8 Optional使@assylias答案更具可读性,

Optional.ofNullable(map.get(key)).ifPresent(value -> {
     //do something with value
};)

2

在Java中,如果您检查实现

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

两者都使用getNode检索匹配,完成主要工作。

冗余是上下文相关的,例如,如果您有一个字典存储在哈希图中。当您想检索单词的含义时

在做...

if(dictionary.containsKey(word)) {
   return dictionary.get(word);
}

是多余的。

但是如果您想根据字典检查单词是否有效。在做...

 return dictionary.get(word) != null;

过度...

 return dictionary.containsKey(word);

是多余的。

如果检查内部使用HashMap的HashSet实现,请在“包含”方法中使用“ containsKey”。

    public boolean contains(Object o) {
        return map.containsKey(o);
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.