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

Eri*_*sen 85 java performance map code-readability

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

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

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

我非常感谢你的评论.

Evg*_*eev 105

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

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

  • 即使将值设置为"null",您是否希望以不同的方式处理未设置的键/值?如果你不需要特别对待它,你可以使用`get()` (8认同)

ass*_*ias 39

我认为这是相当标准的:

Object value = map.get(key);
if (value != null) {
    //do something with value
}
Run Code Online (Sandbox Code Playgroud)

代替

if (map.containsKey(key)) {
    Object value = map.get(key);
    //do something with value
}
Run Code Online (Sandbox Code Playgroud)

它的可读性和效率都不低,所以我没有看到任何不这样做的理由.显然,如果您的映射可以包含null,则这两个选项不具有相同的语义.


Bra*_*don 6

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

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

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


Raj*_*aja 6

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

Optional.ofNullable(map.get(key)).ifPresent(value -> {
     //do something with value
};)
Run Code Online (Sandbox Code Playgroud)


小智 5

  • containsKeyget仅当我们先验地知道永远不会允许空值时,后跟 a才是多余的。如果 null 值无效,则调用 会造成containsKey重大的性能损失,并且只是开销,如下面的基准测试所示。

  • Java 8Optional习惯用法 -Optional.ofNullable(map.get(key)).ifPresent或者Optional.ofNullable(map.get(key)).ifPresent- 与单纯的空检查相比,会产生不小的开销。

  • AHashMap使用O(1)常量表查找,而 aTreeMap使用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));
}
Run Code Online (Sandbox Code Playgroud)
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