使用map.get()时使用java Map.containsKey()是多余的

时间:2013-01-30 09:53:22

标签: java performance code-readability map

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

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

另一方面,Map的大多数标准实现可能会缓存最后一次查找,或者编译器可以以其他方式取消冗余,并且为了代码的可读性,最好保持{ {1}}部分。

我非常感谢你的评论。

6 个答案:

答案 0 :(得分:103)

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

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

答案 1 :(得分:39)

我认为写作是相当标准的:

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,则这两个选项不具有相同的语义

答案 2 :(得分:6)

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

一个这样的案例是缓存。我曾经在Web应用程序中处理性能问题,该应用程序经常查询其数据库以查找不存在的实体。当我研究该组件的缓存代码时,我意识到如果cache.get(key)== null则查询数据库。如果数据库返回null(未找到实体),我们将缓存该密钥 - &gt;空映射。

切换到containsKey解决了这个问题,因为映射到空值实际上意味着什么。键映射到null具有与不存在的键不同的语义含义。

答案 3 :(得分:2)

我们可以使用Java8 Optional,

使@assylias回答更具可读性
Optional.ofNullable(map.get(key)).ifPresent(value -> {
     //do something with value
};)

答案 4 :(得分:1)

在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实现,请在'contains'方法中使用'containsKey'。

    public boolean contains(Object o) {
        return map.containsKey(o);
    }

答案 5 :(得分:1)

    仅当我们知道先决条件永远不允许使用空值时,
  • containsKey后跟get才是多余的。如果空值无效,则containsKey的调用会带来不小的性能损失,并且只是开销,如下面的基准所示。

  • Java 8 Optional习惯用法-Optional.ofNullable(map.get(key)).ifPresentOptional.ofNullable(map.get(key)).ifPresent-与普通null检查相比,产生了不小的开销。

  • HashMap使用O(1)常量表查找,而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));
}
Benchmark                                (iterations)  (lookupApproach)  Mode  Cnt   Score   Error  Units

MultihashTypeLookupBenchmark.testLookup          1000                t1  avgt    9  33.438 ± 4.514  us/op
MultihashTypeLookupBenchmark.testLookup          1000                t2  avgt    9  26.986 ± 0.405  us/op
MultihashTypeLookupBenchmark.testLookup          1000                t3  avgt    9  39.259 ± 1.306  us/op
MultihashTypeLookupBenchmark.testLookup          1000                h1  avgt    9  18.954 ± 0.414  us/op
MultihashTypeLookupBenchmark.testLookup          1000                h2  avgt    9  15.486 ± 0.395  us/op
MultihashTypeLookupBenchmark.testLookup          1000                h3  avgt    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