繁体   English   中英

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

[英]Is using java Map.containsKey() redundant when using map.get()

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

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

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

我非常感谢你的评论。

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

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

我认为这是相当标准的:

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

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

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

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

使用Java8可以使@assylias的答案更具可读性,

Optional.ofNullable(map.get(key)).ifPresent(value -> {
     //do something with value
};)
  • 只有当我们知道apriori永远不会允许空值时, containsKey后跟get才是冗余的。 如果null值无效,则containsKey的调用具有非常重要的性能损失,并且只是开销,如下面的基准测试所示。

  • Java 8 Optional成语 - Optional.ofNullable(map.get(key)).ifPresentOptional.ofNullable(map.get(key)).ifPresent - 与仅仅是vanilla null检查相比,产生了一个非平凡的开销。

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

基准

请参阅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

在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);
    }

暂无
暂无

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

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