文章目录
概要
项目中我们经常会用到Map,也不可避免的需要遍历Map。偶然间刷到一篇文章,不推荐用keySet()遍历HashMap,推荐使用entrySet()进行遍历。给出的理由是keySet()遍历需要经历两次遍历,一次是转为Iterator对象,另一次是是从HashMap中取出key所对应的value;而entrySet()遍历只需要经历一次遍历。这里就不再分析底层的运行逻辑,而是以简单直观的数据运行来展示各遍历方式的效率。
本地环境
处理器:12th Gen Intel® Core™ i5-12500 3.00 GHz
机带RAM:32.0 GB
IDE:Eclipse 2024-09 (4.33.0)
JDK:jdk1.8.0_152
测试数据
准备两对数据(interval分别为1和100):
第一队:1开始,每次增加1,即1,2,3,…
第二队:100开始,每次增加100,即100,200,300,…
准备五组对照组(size分别为1000、10000、100000、1000000和10000000)
数据获取代码如下:
public static Map<String, String> getNumberMap(int size, int interval) {
Map<String, String> map = new HashMap<String, String>();
String key, value;
for (int i = 1; i <= size; i++) {
key = "K" + i * interval;
value = "V" + i * interval;
map.put(key, value);
}
return map;
}
Map遍历方式
keySet的Iterator遍历(key、value)
遍历key
public static void keySetIterator4K(Map<String, String> map) {
Iterator<String> iterator = map.keySet().iterator();
String key;
while (iterator.hasNext()) {
key = iterator.next();
}
}
遍历value
public static void keySetIterator4V(Map<String, String> map) {
Iterator<String> iterator = map.keySet().iterator();
String value;
while (iterator.hasNext()) {
value = map.get(iterator.next());
}
}
遍历key+value
public static void keySetIterator4KV(Map<String, String> map) {
Iterator<String> iterator = map.keySet().iterator();
String key, value;
while (iterator.hasNext()) {
key = iterator.next();
value = map.get(key);
}
}
keySet的for遍历(key、value)
遍历key
public static void keySetFor4K(Map<String, String> map) {
for (String key : map.keySet()) {
}
}
遍历value
public static void keySetFor4V(Map<String, String> map) {
String value;
for (String key : map.keySet()) {
value = map.get(key);
}
}
遍历key+value
public static void keySetFor4KV(Map<String, String> map) {
String value;
for (String key : map.keySet()) {
value = map.get(key);
}
}
entrySet的Iterator遍历(key、value)
遍历key
public static void entrySetIterator4K(Map<String, String> map) {
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
String key;
while (iterator.hasNext()) {
key = iterator.next().getKey();
}
}
遍历value
public static void entrySetIterator4V(Map<String, String> map) {
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
String value;
while (iterator.hasNext()) {
value = iterator.next().getValue();
}
}
遍历key+value
public static void entrySetIterator4KV(Map<String, String> map) {
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
Map.Entry<String, String> entry;
String key, value;
while (iterator.hasNext()) {
entry = iterator.next();
key = entry.getKey();
value = entry.getValue();
}
}
entrySet的for遍历(key、value)
遍历key
public static void entrySetFor4K(Map<String, String> map) {
String key;
for (Map.Entry<String, String> entry : map.entrySet()) {
key = entry.getKey();
}
}
遍历value
public static void entrySetFor4V(Map<String, String> map) {
String value;
for (Map.Entry<String, String> entry : map.entrySet()) {
value = entry.getValue();
}
}
遍历key+value
public static void entrySetFor4KV(Map<String, String> map) {
String key, value;
for (Map.Entry<String, String> entry : map.entrySet()) {
key = entry.getKey();
value = entry.getValue();
}
}
values的Iterator遍历(value)
public static void valuesIterator4V(Map<String, String> map) {
Iterator<String> iterator = map.values().iterator();
String value;
while (iterator.hasNext()) {
value = iterator.next();
}
}
values的for遍历(value)
public static void valuesFor4V(Map<String, String> map) {
for (String value : map.values()) {
}
}
测试工具
public static void main(String[] args) {
int count = 1; // 测试执行次数
int[] size = {1000, 10000, 100000, 1000000, 10000000}; // 数据量
int[] interval = {1, 100}; // 初始及增长
long startTime, endTime;
for (int i = 1; i <= count; i++) {
System.out.println("第" + i + "次测试:");
for (int j = 0; j < size.length; j++) {
System.out.println(" size=" + size[j] + ":");
for (int k = 0; k < interval.length; k++) {
StringBuffer result = new StringBuffer();
Map<String, String> map = getNumberMap(size[j], interval[k]);
/**
* 遍历key
*/
startTime = System.currentTimeMillis();
keySetIterator4K(map); // keySet的Iterator遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
keySetFor4K(map); // keySet的for遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
entrySetIterator4K(map); // entrySet的Iterator遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
entrySetFor4K(map); // entrySet的for遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
/**
* 遍历value
*/
startTime = System.currentTimeMillis();
keySetIterator4V(map); // keySet的Iterator遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
keySetFor4V(map); // keySet的for遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
entrySetIterator4V(map); // entrySet的Iterator遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
entrySetFor4V(map); // entrySet的for遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
valuesIterator4V(map); // values的Iterator遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
valuesFor4V(map); // values的for遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
/**
* 遍历key+value
*/
startTime = System.currentTimeMillis();
keySetIterator4KV(map); // keySet的Iterator遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
keySetFor4KV(map); // keySet的for遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
entrySetIterator4KV(map); // entrySet的Iterator遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
getResultTime(startTime, endTime, result);
startTime = System.currentTimeMillis();
entrySetFor4KV(map); // entrySet的for遍历
endTime = System.currentTimeMillis();
getResultTime(startTime, endTime, result);
System.out.println(" " + result.toString());
}
}
}
}
public static void getResultTime(long startTime, long endTime, StringBuffer result) {
String time = String.valueOf(endTime - startTime);
if (result.toString().isEmpty()) {
result.append(time);
} else {
result.append(" 、" + time);
}
}
数据运行结果
Map遍历方式效率分析 | |||||||||||
遍历场景 | 遍历方式 | 测试数据耗时(ms) | |||||||||
数据量1000 | 数据量10000 | 数据量100000 | 数据量1000000 | 数据量10000000 | |||||||
数据1 | 数据100 | 数据1 | 数据100 | 数据1 | 数据100 | 数据1 | 数据100 | 数据1 | 数据100 | ||
key | keySet的Iterator遍历 | 1 | 0 | 1 | 1 | 5 | 4 | 16 | 27 | 138 | 167 |
keySet的for遍历 | 0 | 0 | 1 | 1 | 3 | 3 | 14 | 25 | 148 | 166 | |
entrySet的Iterator遍历 | 1 | 0 | 2 | 1 | 5 | 4 | 16 | 27 | 153 | 179 | |
entrySet的for遍历 | 0 | 0 | 0 | 1 | 4 | 3 | 15 | 26 | 156 | 177 | |
value | keySet的Iterator遍历 | 0 | 0 | 1 | 0 | 5 | 7 | 19 | 36 | 216 | 212 |
keySet的for遍历 | 0 | 0 | 1 | 1 | 4 | 6 | 20 | 36 | 222 | 215 | |
entrySet的Iterator遍历 | 0 | 1 | 0 | 1 | 4 | 3 | 16 | 26 | 156 | 172 | |
entrySet的for遍历 | 0 | 0 | 0 | 0 | 4 | 4 | 15 | 26 | 160 | 175 | |
values的Iterator遍历 | 1 | 0 | 1 | 1 | 4 | 3 | 15 | 25 | 150 | 162 | |
values的for遍历 | 0 | 0 | 0 | 0 | 4 | 3 | 14 | 25 | 148 | 162 | |
key+value | keySet的Iterator遍历 | 0 | 0 | 1 | 0 | 5 | 5 | 20 | 37 | 210 | 214 |
keySet的for遍历 | 0 | 0 | 1 | 0 | 5 | 5 | 19 | 37 | 213 | 210 | |
entrySet的Iterator遍历 | 0 | 0 | 1 | 0 | 5 | 4 | 16 | 27 | 164 | 178 | |
entrySet的for遍历 | 1 | 0 | 1 | 0 | 4 | 3 | 17 | 28 | 168 | 177 |
总结
对比上面表格做出以下总结:
1、当Map的size在100000及以下时,各种遍历方式之间差别不大
2、当Map的size为100000,字符串长度最大为9时,多数遍历方式出现字符串长度大遍历效率高(?)
3、当Map的size为100w以上时,可以明显看出keySet的Iterator遍历和for遍历在遍历value时耗时高于其他遍历方式
4、当Map的size为100w以上时,字符串长度也会影响遍历效率
所以,遍历HashMap时,如果只是遍历key或者数据量小于10000(?)时,那么无论哪种方式都是可以使用的;如果涉及到value的遍历,那么尽量避免使用keySet()相关的遍历。
补充
1、Map初始化
HashMap使用HashMap(int initialCapacity) 初始化,如果暂时无法确定集合大小,那么指定默认值(16)即可。如果没有设置容量初始大小,随着元素增加而被迫不断扩容,resize()方法会多次调用,反复重建哈希表和数据迁移。当放置的集合元素个数达千万级时会影响程序性能。
例如调整一下测试数据中数据获取的代码,初始化Map时设置大小:
public static Map<String, String> getNumberMap(int size, int interval) {
Map<String, String> map = new HashMap<String, String>(size);
String key, value;
for (int i = 1; i <= size; i++) {
key = "K" + i * interval;
value = "V" + i * interval;
map.put(key, value);
}
return map;
}
再进行测试,结果如下:
对比可以看到Map初始化时,指定初始大小对于遍历Map效率的影响会随着数量级增长而变大。