如果Map中有大量的元素,而且并发量又很高,这就涉及到采用哪种遍历方法的问题,下面就来测试一下:
public class Test {
public static void main(String[] args) {
Map<String, String> mapTest = new HashMap<String, String>();
for (int i = 0; i < 10000; i++) {
mapTest.put( String.valueOf( i ), String.valueOf( i ) );
}
// 第一种遍历,keySet()方法
long start = System.nanoTime();
Set<String> keySet = mapTest.keySet();
for (String key : keySet) {
// 获取value时,会进行两次hashCode的计算,消耗CPU资源
// 键取值是耗时的操作(与方法三相比,在不同的Map实现中该方法慢了20%~200%)
String value = mapTest.get( key );
}
long end = System.nanoTime();
System.out.println( "keySet遍历map耗时:\t\t\t" + (end - start) / 1000 + "微秒" );
// 第二种遍历,可用values()返回Collection<T>
// 该方法比entrySet遍历在性能上稍好(快了10%),而且代码更加干净 但不容易得到对应的key
start = System.nanoTime();
Collection<String> co = mapTest.values();
for (String value : co) {
// 遍历中也在创建value
}
end = System.nanoTime();
System.out.println( "values遍历map(只得到值)耗时:\t\t" + (end - start) / 1000 + "微秒" );
// 第三种遍历,用entrySet()方法返回Set<Map.Entry<T,T>>类型,再获取里边的Map.Entry
// 打算在遍历时删除entries,可使用entrySet()的迭代器
// map对象会直接返回其保存key-value的原始数据结构对象,遍历过程无需进行错误代码中耗费时间的hashCode计算;在大数据量体现的尤为明显
start = System.nanoTime();
Set<Map.Entry<String, String>> entrySet = mapTest.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
// String key = entry.getKey();
String value = entry.getValue();
}
end = System.nanoTime();
System.out.println( "entrySet遍历map耗时:\t\t" + (end - start) / 1000 + "微秒" );
// 迭代器的遍历就此省略......................
}
}
经过多次运行,结果大概都是这样的:
keySet遍历map耗时: 7710微秒
values遍历map(只得到值)耗时: 2394微秒
entrySet遍历map耗时: 3476微秒
values()是返回Map的所有value的集合collection,只能遍历到值,很难遍历到key所以一般不用,除非在某种特殊场合,所以一般采用的第一种和第三种方式。而测试表明entrySet()方式遍历效率更高。
entrySet()方式遍历之所以快与keySet(),一个原因是keySet相当与遍历了2次,一次是对key的Set集合的遍历,二次是每次遍历过程都要通过key和map.get(key)来获取value值。第二个原因是map.get(key)获取的时候,底层其实根据key的hashcode值经过哈希算法得到一个hash值然后作为索引映射到对应table数组的索引位置,这是一次密集型计算,很耗费CPU,如果有大量的元素,则会使CPU使用率飙升,影响响应速度,而entrySet()返回的set里边元素都是Map.Entry类型,key和value就是这个类的一个属性,entry.getKey()和entry.getValue()效率肯定很高。
所以平常开发过程中,如果对Map讲究效率的遍历的话,还是采用entrySet()方法
以下是HashMap.get()方法的源码:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
结论:
3.1 如果你使用HashMap
1.同时遍历key和value时,keySet与entrySet方法的性能差异取决于key的具体情况,如复杂度(复杂对象)、离散度、冲突率等。换言之,取决于HashMap查找value的开销。entrySet一次性取出所有 key和value的操作是有性能开销的,当这个损失小于HashMap查找value的开销时,entrySet的性能优势就会体现出来。例如上述对比测试中,当key是最简单的数值字符串时,keySet可能反而会更高效,耗时比entrySet少10%。总体来说还是推荐使用entrySet。因为当key很简单时,其性能或许会略低于keySet,但却是可控的;而随着key的复杂化,entrySet的优势将会明显体现出来。当然,我们可以根据实际情况进行选择
2.只遍历key时,keySet方法更为合适,因为entrySet将无用的value也给取出来了,浪费了性能和空间。在上述测试结果中,keySet比entrySet方法耗时少23%。
3.只遍历value时,使用vlaues方法是最佳选择,entrySet会略好于keySet方法。
3.2 如果你使用TreeMap
1.同时遍历key和value时,与HashMap不同,entrySet的性能远远高于keySet。这是由TreeMap的查询效率决定的,也就是说,TreeMap查找value的开销较大,明显高于entrySet一次性取出所有key和value的开销。因此,遍历TreeMap时强烈推荐使用entrySet方法。
2.只遍历key时,keySet方法更为合适,因为entrySet将无用的value也给取出来了,浪费了性能和空间。在上述测试结果中,keySet比entrySet方法耗时少24%。
3.只遍历value时,使用vlaues方法是最佳选择,entrySet也明显优于keySet方法。