【问题标题】:Is using java Map.containsKey() redundant when using map.get()使用 map.get() 时使用 java Map.containsKey() 是多余的
【发布时间】:2013-01-14 02:13:09
【问题描述】:

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

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

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

非常感谢您的 cmets。

【问题讨论】:

    标签: java performance code-readability map


    【解决方案1】:

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

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

    【讨论】:

    • 即使值设置为null,您是否希望将其与未设置的键/值区别对待?如果您没有特别需要区别对待,您可以使用get()
    • 如果您的Mapprivate,您的班级可能能够保证null 永远不会插入到地图中。在这种情况下,您可以使用get(),然后检查空值而不是containsKey()。在某些情况下,这样做会更清晰,甚至可能更有效。
    【解决方案2】:

    我觉得这样写比较标准:

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • 有趣。为什么不在缓存值之前简单地添加一个空检查?
      • 这不会改变任何事情。关键是到 null 的键映射意味着“我们已经这样做了。它被缓存了。值 is null”。与根本不包含给定键的情况相比,这意味着“不知道,不在缓存中,我们可能需要检查数据库。”
      【解决方案4】:
      • containsKey 后跟 get 是多余的,前提是我们先验地知道永远不会允许空值。如果空值无效,则containsKey 的调用会带来不小的性能损失,并且只是开销,如下面的基准所示。

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

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

      基准

      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

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          如果你检查实现,在 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);
          

          是多余的。

          如果您检查 HashSet 实现,它在内部使用 HashMap,请在 'contains' 方法中使用 'containsKey'。

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

          【讨论】:

            猜你喜欢
            • 2014-06-19
            • 2017-04-08
            • 1970-01-01
            • 2023-03-31
            • 2012-04-02
            • 1970-01-01
            • 1970-01-01
            • 2014-12-21
            • 1970-01-01
            相关资源
            最近更新 更多