一段时间以来,我一直想知道在最佳实践中是否允许避免使用containsKey()
on 方法java.util.Map
,而是对来自get()
.
我的理由是,对值进行两次查找似乎是多余的——首先是 the containsKey()
,然后是get()
。
另一方面,Map
缓存最后一次查找的大多数标准实现可能是缓存,或者编译器可以以其他方式消除冗余,并且为了代码的可读性,最好保留该containsKey()
部分。
非常感谢您的意见。
一段时间以来,我一直想知道在最佳实践中是否允许避免使用containsKey()
on 方法java.util.Map
,而是对来自get()
.
我的理由是,对值进行两次查找似乎是多余的——首先是 the containsKey()
,然后是get()
。
另一方面,Map
缓存最后一次查找的大多数标准实现可能是缓存,或者编译器可以以其他方式消除冗余,并且为了代码的可读性,最好保留该containsKey()
部分。
非常感谢您的意见。
某些 Map 实现允许具有空值,例如 HashMap,在这种情况下,如果get(key)
返回null
它不保证在映射中没有与此键关联的条目。
因此,如果您想知道地图是否包含密钥 use Map.containsKey
。如果您只需要映射到键的值,请使用Map.get(key)
. 如果此映射允许 null 值,则返回值为 null 并不一定表示该映射不包含该键的映射;在这种情况下Map.containsKey
是无用的并且会影响性能。此外,在并发访问地图的情况下(例如ConcurrentHashMap
),在您测试之后Map.containsKey(key)
,该条目有可能在您调用之前被另一个线程删除Map.get(key)
。
我认为这是相当标准的写法:
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,则这两个选项的语义不同。
正如 assylias 所指出的,这是一个语义问题。通常,Map.get(x) == null 是您想要的,但在某些情况下使用 containsKey 很重要。
一种这样的情况是缓存。我曾经在一个 Web 应用程序中处理性能问题,该应用程序经常查询其数据库以查找不存在的实体。当我研究该组件的缓存代码时,我意识到它正在查询数据库 if cache.get(key) == null。如果数据库返回 null(未找到实体),我们将缓存该键 -> null 映射。
切换到 containsKey 解决了这个问题,因为映射到空值实际上意味着什么。到 null 的键映射与不存在的键具有不同的语义。
containsKey
get
仅当我们先验地知道永远不会允许空值时,后跟 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
a 上调用时,后跟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));
}
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
https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/TreeMap.java
https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/HashMap.java
我们可以使用 Java8 Optional 使@assylias 的答案更具可读性,
Optional.ofNullable(map.get(key)).ifPresent(value -> {
//do something with value
};)
在 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);
是多余的。
如果您检查内部使用 HashMap 的HashSet实现,请在 'contains' 方法中使用 'containsKey'。
public boolean contains(Object o) {
return map.containsKey(o);
}