Map遍历方式效率分析

概要

项目中我们经常会用到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
keykeySet的Iterator遍历1011541627138167
keySet的for遍历0011331425148166
entrySet的Iterator遍历1021541627153179
entrySet的for遍历0001431526156177
valuekeySet的Iterator遍历0010571936216212
keySet的for遍历0011462036222215
entrySet的Iterator遍历0101431626156172
entrySet的for遍历0000441526160175
values的Iterator遍历1011431525150162
values的for遍历0000431425148162
key+valuekeySet的Iterator遍历0010552037210214
keySet的for遍历0010551937213210
entrySet的Iterator遍历0010541627164178
entrySet的for遍历1010431728168177

总结

对比上面表格做出以下总结:
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效率的影响会随着数量级增长而变大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值