关于java:HashMap中的密钥存在检查

Key existence check in HashMap

是否总是需要在HashMap中检查密钥是否存在?

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

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

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


你有没有存储空值?如果没有,你可以这样做:

1
2
3
4
5
6
Foo value = map.get(key);
if (value != null) {
    ...
} else {
    // No such key
}

否则,如果返回null值,则可以检查是否存在:

1
2
3
4
5
6
7
8
9
10
11
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
    }
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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源代码。

资源:

  • HashMap源代码糟糕的一个
  • HashMap源代码很好


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


你是说你有像这样的代码

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

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


只需使用containsKey()即可。它速度快,保持代码清洁和可读性。 HashMap s的重点是密钥查找速度很快,只需确保hashCode()equals()正确实现。


1
if(map.get(key) != null || (map.get(key) == null && map.containsKey(key)))


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

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

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

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

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

真的很优雅! ??


Jon Skeet的答案很好地解决了这两种情况(使用null值而不是null值映射)。

关于数字条目和效率问题,我想补充一些东西。

I have a HashMap with say a 1.000 entries and I am looking at improving
the efficiency. If the HashMap is being accessed very frequently, then
checking for the key existence at every access will lead to a large
overhead.

包含1.000个条目的地图不是一张巨大的地图。
以及具有5.000或10.000条目的地图。
Map旨在通过这样的尺寸进行快速检索。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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中经常使用的,你可能会有一些碰撞,但在Map中有一千到几千个对象,你应该只有很少的,因为String.hashCode()方法提供了良好的分布。

如果您使用自定义类型,请正确覆盖hashCode()equals(),并确保hashCode()提供公平分配。
您可以参考Java Effective的第9项引用它。
这是一篇详细说明方式的帖子。


  • 如果是关键类,请确保实现了hashCode()和equals()方法。
  • 基本上,对HashMap的访问应该是O(1),但是使用错误的hashCode方法实现它变为O(n),因为具有相同散列键的值将存储为链接列表。

  • 我通常使用这个成语

    1
    2
    3
    4
    5
    Object value = map.get(key);
    if (value == null) {
        value = createValue(key);
        map.put(key, value);
    }

    这意味着如果缺少密钥,则只会按两次地图