我一直想知道,在最佳实践中,是否允许不使用containsKey()
on方法java.util.Map
,而是对from的结果进行空检查get()
。
我的理由是,对值进行两次查找似乎是多余的:首先对进行查找,containsKey()
然后对进行查找get()
。
另一方面,可能是大多数标准实现都Map
缓存了最后的查找,或者编译器可以通过其他方式消除冗余,并且对于代码的可读性而言,最好保留该containsKey()
部分。
非常感谢您的评论。
我一直想知道,在最佳实践中,是否允许不使用containsKey()
on方法java.util.Map
,而是对from的结果进行空检查get()
。
我的理由是,对值进行两次查找似乎是多余的:首先对进行查找,containsKey()
然后对进行查找get()
。
另一方面,可能是大多数标准实现都Map
缓存了最后的查找,或者编译器可以通过其他方式消除冗余,并且对于代码的可读性而言,最好保留该containsKey()
部分。
非常感谢您的评论。
Answers:
某些Map实现被允许具有空值,例如HashMap,在这种情况下,如果get(key)
返回null
,则不能保证地图中没有与此键关联的条目。
因此,如果您想知道地图是否包含键use Map.containsKey
。如果仅需要映射到键的值,请使用Map.get(key)
。如果此映射允许空值,则返回值null不一定表示该映射不包含键的映射。在这种情况下Map.containsKey
是无用的,会影响性能。此外,在并发访问地图的情况下(例如ConcurrentHashMap
),在测试之后,您Map.containsKey(key)
有可能在调用之前由另一个线程删除该条目Map.get(key)
。
Map
是private
,则您的课程可能能够保证a null
不会插入到地图中。在这种情况下,您可以使用,get()
然后再检查是否为null而不是containsKey()
。在某些情况下,这样做可能会更清晰,甚至更有效率。
正如sysylias指出的,这是一个语义问题。通常,您想要的是Map.get(x)== null,但是在某些情况下,使用containsKey很重要。
一种这样的情况是高速缓存。我曾经在一个Web应用程序中处理过一个性能问题,该应用程序经常查询其数据库以查找不存在的实体。研究该组件的缓存代码时,我意识到如果cache.get(key)== null,它正在查询数据库。如果数据库返回空值(找不到实体),我们将缓存该键->空值映射。
切换到containsKey解决了该问题,因为映射到null值实际上意味着某些事情。映射到null的键具有与不存在的键不同的语义。
containsKey
get
仅当我们先验地知道绝不会允许使用null值时,后接a 才是多余的。如果null值无效,则的调用containsKey
会带来不小的性能损失,并且只是开销,如下面的基准所示。
与仅空值检查相比,Java 8 Optional
惯用语- Optional.ofNullable(map.get(key)).ifPresent
或Optional.ofNullable(map.get(key)).ifPresent
-产生了不小的开销。
A HashMap
使用O(1)
常量表查找,而A TreeMap
使用O(log(n))
查找。在上调用时,containsKey
后面跟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));
}
基准(迭代)(lookupApproach)模式Cnt得分错误单位 MultihashTypeLookupBenchmark.testLookup 1000 t1平均9 33.438±4.514 us / op MultihashTypeLookupBenchmark.testLookup 1000 t2平均9 26.986±0.405 us / op MultihashTypeLookupBenchmark.testLookup 1000 t3平均9 39.259±1.306 us / op MultihashTypeLookupBenchmark.testLookup 1000 h1平均9 18.954±0.414 us / op MultihashTypeLookupBenchmark.testLookup 1000 h2平均9 15.486±0.395 us / op MultihashTypeLookupBenchmark.testLookup 1000 h3平均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
在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实现,请在“包含”方法中使用“ containsKey”。
public boolean contains(Object o) {
return map.containsKey(o);
}
null
,您是否也要对未设置的键/值区别对待?如果您不需要特别对待,可以使用get()