HashMap中的密钥存在检查


309

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

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

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

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


8
“因此而发生异常”-什么异常?这不会来自java.util.HashMap ...
serg10'9

Answers:


513

您是否存储了空值?如果没有,您可以执行以下操作:

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
    }
}

1
@Samuel:仅当null为可能值时。如果您在地图中绝对没有空值,那就get很好,并且在需要该值时也避免进行两次查找。
乔恩·斯基特

虽然这可能更清楚一些,但您也可以if(value!=null || map.containsKey(key))为第二部分编写。至少如果您想以任何一种方式做同样的事情-无需重复代码。由于短路会起作用。
Cullub

66

通过检查密钥是否存在,您将不会获得任何收益。这是以下代码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源代码。


资源:


2
显示这些方法的一种具体实现有什么意义?
jarnbjo 2010年

2
为了说明这一点,在大多数情况下,检查键是否存在与获取值大约需要相同的时间。因此,在获取值之前,它不会对检查键是否存在进行任何优化。我知道这是一个概括,但可以帮助您理解。
科林·赫伯特

一个很好的链接是grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…(OpenJDK非常强地源自Sun代码),似乎我错了。我在比较Java5和Java6的版本;它们在此区域的工作方式有所不同(但两者都是正确的,您发布的代码段也是如此)。
Donal Fellows 2010年

2
正如公认的答案中指出的那样,此举是完全错误的。当然,您可以通过检查比较值的键是否存在来获得某些收益-您可以将不存在的键与已存在但作为值映射为null的键区分开。
Johannes H.

43

更好的方法是使用的containsKey方法HashMap。明天有人会在地图上添加null。您应该区分键的存在和键的值为空。


是的 或对HashMap进行子类化以防止将其存储null在一起。
RobAu 2015年

1
原始类型为1+,因为使用此答案不需要值进行不必要的
强制转换

写.containsKey()比检查null更流利。在大多数情况下,我们要担心的是易于阅读,从而节省了开发人员的时间,而不是进行一些次要的优化。至少在必要之前没有进行优化。
Max

23

你是说你有类似的代码

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

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


8

只是containsKey()为了清楚起见。它速度很快,并保持代码的清洁和可读性。HashMaps 的全部要点是密钥查找是快速的,只需确保hashCode()equals()正确实现即可。


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

3

您也可以computeIfAbsent()HashMap类中使用该方法。

在下面的示例中,map存储应用于密钥(银行帐户名称)的交易(整数)列表。要添加的2个交易100,并200checking_account可以这样写:

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

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

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

真的很优雅!👍


0

我通常使用成语

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

这意味着如果钥匙丢失,您只会打两次地图


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

0

乔恩·斯凯特(Jon Skeet)的答案以有效的方式很好地解决了两种情况(带有null价值和没有null价值的地图)。

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

我有一个带有1.000个条目的HashMap,并且我正在考虑提高效率。如果HashMap的访问非常频繁,则每次访问时检查密钥是否存在将导致大量开销。

包含1.000个条目的地图不是很大的地图。
以及具有5.000或10.000条目的地图。
Map设计用于以这种尺寸进行快速检索。

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

如果您可以使用Integeras作为键类型,请执行此操作。
它的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可能会发生一些冲突,但是从中的1000个对象到数千个对象Map,则该String.hashCode()方法应该很少提供良好的分布。

如果您使用自定义类型,请覆盖hashCode()equals()正确进行设置,并确保总体hashCode()提供合理的分布。
您可以参考的第9项Java Effective
这里有一个帖子,详细介绍了道路。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.