HashMap 几种遍历方式总结

话不多说直接上代码
测试环境:jdk1.8u_121 + win7

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public static void fun2() {

HashMap<String,String> map = new HashMap();
for (int i = 0; i < 10000000; i++) {
map.put(i+"","test");
}

//方式一:foreach + keySet
long t0 = System.nanoTime();
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.print(key + ":" + map.get(key)+" ");
}
long t11 = System.nanoTime() - t0;

//方式二: foreach + entrySet
long t1 = System.nanoTime();
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> entries : entrySet) {
System.out.print(entries.getKey() + ":" + entries.getValue()+" ");
}
long t22 = System.nanoTime() - t1;

//方式三: while + entrySet + iterator
long t2 = System.nanoTime();
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.print(entry.getKey() + ":" + entry.getValue()+" ");
}
long t33 = System.nanoTime() - t2;

//方式四:jdk8的forEach 方式
long t3 = System.nanoTime();
map.forEach((k,v)->System.out.print(k + ":" + v+" "));
long t44 = System.nanoTime() - t3;

System.out.println("方式1:foreach + keySet:"+t11);
System.out.println("方式2:foreach + entrySet:"+t22);
System.out.println("方式3: while + entrySet + iterator:"+t33);
System.out.println("方式4: jdk1.8 foreach:"+t44);
}

测试发现:

在map的size比较小的情况下, 比如100,执行时间(单位:纳秒): 方式3 < 方式2 < 方式1 < 方式4

1
2
3
4
5
输出:
方式1:foreach + keySet:1317960
方式2:foreach + entrySet:940020
方式3: while + entrySet + iterator:489933
方式4: jdk1.8 forEach:39953271

可以发现,jdk1.8 forEach 效率很低,而while + entrySet + iterator效率较高。

再来看map的size()比较大的情况下,比如 10000000 , 执行时间(单位:纳秒): 方式4 < 方式2 < 方式3 < 方式1

1
2
3
4
5
输出:
方式1:foreach + keySet:20879939144
方式2:foreach + entrySet:15610941847
方式3: while + entrySet + iterator:15204709459
方式4: jdk1.8 foreach:15176818875

可以发现,jdk1.8 forEach 效率变得最高。其次是while + entrySet + iteratorforeach + entrySet效率最高。

所以一般代码中当数据量很大时可以使用jdk8的forEach方法,而如果数据量不是很大又对性能要求比较高的话,推荐使用 while + entrySet + iterator方式。

foreach + iterator的编译优化

用ideal去看编译后的class文件, foreach + entrySet的方式会转成while + entrySet + iterator的方式

keySetentrySet的区别

keySet

keySet 一次遍历获取出key的set集合,然后通过key,调用map.get(key) 方法,get中会有一些求hashcode,异或的运算,所以比较费时。

部分源码:遍历获取键

1
2
3
4
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}

根据key获取value:

1
2
3
4
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}

entrySet

entrySet一次遍历获取去entry数组,包括键和值,获取值时直接return没有别的操作。

部分源码:遍历获取node(node实现了entry接口)

1
2
3
final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}

获取键和值

1
2
3
4
5
static class Node<K,V> implements Map.Entry<K,V> {
......
public final K getKey() { return key; }
public final V getValue() { return value; }
}

待补充…