【问题标题】:Key existence check in HashMapHashMap 中的键存在检查
【发布时间】:2011-04-07 07:20:43
【问题描述】:

是否总是需要检查 HashMap 中的键是否存在?

我有一个包含 1000 个条目的 HashMap,我正在考虑提高效率。 如果 HashMap 被非常频繁地访问,那么在每次访问时检查 key 是否存在将导致很大的开销。相反,如果密钥不存在并因此发生异常,我可以捕获异常。 (当我知道这种情况很少发生时)。这将使对 HashMap 的访问减少一半。

这可能不是一个好的编程习惯,但它会帮助我减少访问次数。还是我在这里遗漏了什么?

[更新] 我在 HashMap 中没有空值。

【问题讨论】:

  • “因此发生异常” - 什么异常?这不会来自 java.util.HashMap...

标签: java hashmap


【解决方案1】:

您曾经存储过空值吗?如果没有,你可以这样做:

Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // No such key
}

否则,可以在返回空值时检查是否存在:

Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // Key might be present...
    if (map.containsKey(key)) {
       // Okay, there's a key but the value is null
    } else {
       // Definitely no such key
    }
}

【讨论】:

  • @Samuel:仅当 null 是可能的值时。如果地图中绝对没有空值,只需get 即可,并且在您需要该值时避免进行两次查找。
  • 虽然这个例子可能更清楚,但你也可以写if(value!=null || map.containsKey(key))作为第二部分。至少如果你想以任何一种方式做同样的事情——没有重复的代码。由于short circuiting,它将起作用。
【解决方案2】:

通过检查密钥是否存在,您不会获得任何收益。这是HashMap的代码:

@Override
public boolean containsKey(Object key) {
    Entry<K, V> m = getEntry(key);
    return m != null;
}

@Override
public V get(Object key) {
    Entry<K, V> m = getEntry(key);
    if (m != null) {
        return m.value;
    }
    return null;
}

只需检查get() 的返回值是否与null 不同。

这是HashMap源代码。


资源:

【讨论】:

  • 展示这些方法的一种具体实现有什么意义?
  • 解释一下,在大多数情况下,检查密钥是否存在与获取值所花费的时间大致相同。因此,在获取值之前,它不会优化任何东西来检查密钥是否实际存在。我知道这是一个概括,但它可以帮助理解。
  • 一个好的链接是grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… (OpenJDK 非常强烈地源自 Sun 代码),看来我错了。我正在比较 Java5 和 Java6 的版本;它们在这方面的工作方式不同(但两者都是正确的,您发布的 sn-ps 也是如此)。
  • 正如在接受的答案中指出的那样,这个snawer 是完全错误的。当然,您确实可以通过检查键是否存在或比较值来获得一些好处 - 您可以将不存在的键与存在但作为值映射到 null 的键区分开来。
【解决方案3】:

更好的方法是使用HashMapcontainsKey 方法。明天有人会将 null 添加到地图中。您应该区分密钥存在和密钥具有空值。

【讨论】:

  • 是的。或者子类化 HashMap 以防止将null 存储在一起。
  • 1+ 用于原始类型,因为使用此答案不需要不必要的强制转换
  • 写.containsKey()也比检查null更流畅。在大多数情况下,我们应该更多地担心易于阅读,这可以节省开发人员的时间,而不是一些小的优化。至少在必要之前不要优化。
【解决方案4】:

你的意思是你有类似的代码

if(map.containsKey(key)) doSomethingWith(map.get(key))

到处都是?然后你应该简单地检查 map.get(key) 是否返回 null 就是这样。 顺便说一句,HashMap 不会为丢失的键抛出异常,而是返回 null。唯一需要containsKey 的情况是当您存储空值时,以区分空值和缺失值,但这通常被认为是不好的做法。

【讨论】:

    【解决方案5】:

    为了清楚起见,只需使用containsKey()。它速度快,并保持代码干净易读。 HashMaps 的重点在于密钥查找速度很快,只要确保正确实现了 hashCode()equals()

    【讨论】:

      【解决方案6】:
      if(map.get(key) != null || (map.get(key) == null && map.containsKey(key)))
      

      【讨论】:

        【解决方案7】:

        您还可以在HashMap 类中使用computeIfAbsent() 方法。

        在以下示例中,map 存储应用于密钥(银行账户名称)的交易(整数)列表。要将100200 的2 个事务添加到checking_account,您可以这样写:

        HashMap<String, ArrayList<Integer>> map = new HashMap<>();
        map.computeIfAbsent("checking_account", key -> new ArrayList<>())
           .add(100)
           .add(200);
        

        这样您就不必检查密钥checking_account 是否存在。

        • 如果它不存在,则会创建一个并由 lambda 表达式返回。
        • 如果存在,则该键的值将由computeIfAbsent() 返回。

        真的很优雅! ?

        【讨论】:

          【解决方案8】:

          我通常用成语

          Object value = map.get(key);
          if (value == null) {
              value = createValue(key);
              map.put(key, value);
          }
          

          这意味着如果钥匙丢失,您只需点击地图两次

          【讨论】:

            【解决方案9】:
            1. 如果关键类是您的,请确保已实现 hashCode() 和 equals() 方法。
            2. 基本上 HashMap 的访问应该是 O(1),但是如果 hashCode 方法实现错误,它就会变成 O(n),因为具有相同哈希键的值将存储为链表。

            【讨论】:

              【解决方案10】:

              The Jon Skeet answer 以一种有效的方式很好地解决了这两种情况(映射具有 null 值而不是 null 值)。

              关于数量条目和效率问题,我想补充一点。

              我有一个包含 1.000 个条目的 HashMap,我正在考虑改进 效率。如果 HashMap 被非常频繁地访问,那么 在每次访问时检查密钥是否存在将导致大量 开销。

              包含 1.000 个条目的地图并不是一张巨大的地图。
              以及包含 5.000 或 10.000 个条目的地图。
              Map 旨在使用此类尺寸进行快速检索。

              现在,它假设 hashCode() 的映射键提供了良好的分布。

              如果您可以使用 Integer 作为密钥类型,请执行此操作。
              它的hashCode() 方法非常有效,因为唯一的int 值不可能发生冲突:

              public final class Integer extends Number implements Comparable<Integer> {
                  ...
                  @Override
                  public int hashCode() {
                      return Integer.hashCode(value);
                  }
              
                  public static int hashCode(int value) {
                      return value;
                  }
                  ...
              }
              

              如果对于密钥,您必须使用另一个内置类型 String,例如在 Map 中经常使用的,您可能会遇到一些冲突,但在 @987654333 中可能会有 1000 到数千个对象@,你应该很少有它,因为String.hashCode() 方法提供了良好的分布。

              如果您使用自定义类型,请正确覆盖 hashCode()equals(),并确保总体上 hashCode() 提供公平分布。
              您可以参考Java Effective的第9项。
              这是一个post,详细说明了方式。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-06-18
                • 2018-11-28
                • 2016-04-10
                • 1970-01-01
                相关资源
                最近更新 更多