Java集合篇:Map集合的几种遍历方式及性能测试

此文转载自:https://blog.csdn.net/shaofengzong/article/details/113610995#commentBox

一、写在前面

	好久不见。
	一转眼,新的一年已经走过了两个月,距离上次更新博客也相隔好几个月,原本打算月更的我,也因为工作
	和懒散,走上了拖更之路。
	不过,我小宗又回来了!

今天,让我们来探究一个老生常谈的问题:Map集合的遍历。这同时也是许多Java初学者必须要掌握的基础知识,网上也有很多大神很多博客对此进行讲解。此博文从知识层面上借鉴之,并记录于2021年2月3日16:13:15。


二、正式开始探究之旅

1. Map集合一共有多少种遍历方式呢?

Map集合主要有三种遍历方式:keySet()、entrySet()、values()。但是,如果从API层面上进行细分的话有7种。这三种各自都有两种形式,for循环和Iterator迭代。还有最后一种,如果你是JDK8以上版本,还可以使用Lambda表达式forEach遍历

2.那这几种遍历方式的具体用法是啥样的呢?

下面,我使用IDEA新建一个项目,进行demo编写演示。具体的IDEA操作不属于本文的研究重点,略去不表。直接在test下面写测试案例。

2.1 keySet()方式遍历-------for循环

		//2.1 keySet()方式遍历-------for循环
        long keySetForStartTime = System.nanoTime();
        for (String key : map.keySet()) {
            map.get(key);
        }
        long keySetForEndTime = System.nanoTime();

2.2 keySet()方式遍历-------Iterator迭代

		//2.2 keySet()方式遍历-------Iterator迭代
        long keySetIteratorStartTime = System.nanoTime();
        Iterator<String> iterator1 = map.keySet().iterator();
        while (iterator1.hasNext()) {
            String key = iterator1.next();
            map.get(key);
        }
        long keySetIteratorEndTime = System.nanoTime();

2.3 entrySet()方式遍历-------for循环

		long entrySetForStartTime = System.nanoTime();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            entry.getKey();
            entry.getValue();
        }
        long entrySetForEndTime = System.nanoTime();

2.4 entrySet()方式遍历-------Iterator迭代

		long entrySetIteratorStartTime = System.nanoTime();
        Iterator<Map.Entry<String, String>> iterator2 = map.entrySet().iterator();
        while (iterator2.hasNext()) {
            Map.Entry<String, String> entry = iterator2.next();
            entry.getKey();
            entry.getValue();
        }
        long entrySetIteratorEndTime = System.nanoTime();

2.5 values()方式遍历-------for循环

		long valuesForStartTime = System.nanoTime();
        Collection<String> values = map.values();
        for (String value : values) {
            //.....
        }
        long valuesForEndTime = System.nanoTime();

2.6 values()方式遍历-------Iterator迭代

		//2.6 values()方式遍历-------Iterator迭代
        long valuesIteratorStartTime = System.nanoTime();
        Iterator<String> iterator3 = map.values().iterator();
        while (iterator3.hasNext()) {
            String value = iterator3.next();
        }
        long valuesIteratorEndTime = System.nanoTime();

2.7 JDK8-------Lambda表达式forEach遍历

		//2.7 JDK8-------Lambda表达式forEach遍历
        long forEachStartTime = System.nanoTime();
        map.forEach((key, value) -> {
            //......
        });
        long forEachEndTime = System.nanoTime();

JDK8Lambda表达式的forEach方法,其实就是一种语法糖,让你的代码更加简洁,使用更加方便,深入源码,我们可以很轻易的发现,它其实就是对entrySet遍历方式的一种包装而已。不信你看下面我贴的forEach源码。

forEach源码。本方法since1.8版本

     * @param action The action to be performed for each entry
     * @throws NullPointerException if the specified action is null
     * @throws ConcurrentModificationException if an entry is found to be
     * removed during iteration
     * @since 1.8
     */
    default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
    }

3. 那这几种遍历方式的性能如何呢?哪一种更推荐使用呢?

3.1首先,我们来准备一下测试数据

我们实例化一个HashMap对象,往Map中添加一百万条数据,以此作为测试数据。

		//准备Map,装入测试数据
        Map<String, String> map = new HashMap<>();
        //往map里插入一百万数据,作为测试数据,别怕,家里就这条件
        for (int i = 0; i < 1000000; i++) {
            map.put(String.valueOf(i), "我是第" + i + "名");
        }

3.2打印各种遍历方式遍历测试数据的执行时间

由于在上一部分的API使用的代码中,我们已经穿插进去计算执行时间的代码,下面我们可以直接打印时间差,作为每种遍历方式的执行时间,虽然不是很严格,但是也能从结果中看得比较明显。

		System.out.println("keySet()方式遍历-------for循环用时:" + (keySetForEndTime - keySetForStartTime) / 1000000 + "毫秒");
        System.out.println("keySet()方式遍历-------Iterator迭代用时:" + (keySetIteratorEndTime - keySetIteratorStartTime) / 1000000 + "毫秒");
        System.out.println("entrySet()方式遍历-------for循环用时:" + (entrySetForEndTime - entrySetForStartTime) / 1000000 + "毫秒");
        System.out.println("entrySet()方式遍历-------Iterator迭代用时:" + (entrySetIteratorEndTime - entrySetIteratorStartTime) / 1000000 + "毫秒");
        System.out.println("values()方式遍历-------for循环用时:" + (valuesForEndTime - valuesForStartTime) / 1000000 + "毫秒");
        System.out.println("values()方式遍历-------Iterator迭代用时:" + (valuesIteratorEndTime - valuesIteratorStartTime) / 1000000 + "毫秒");
        System.out.println("JDK8-------Lambda表达式forEach遍历用时:" + (forEachEndTime - forEachStartTime) / 1000000 + "毫秒");

3.3测试结果

测试结果
结果分析:
直接抛出结论,entrySet()方式比keySet()方式效率更高,在忽略其他条件下,对于同一种遍历方式而言,Iterator迭代比for循环效率高。

当然,上述的结论只是说出了一半。其实是分两种情况的。在元素数量大的情况下,entrySet()性能确实是优于keySet()的,越大越明显。同样的,在小数据量的情况下,keySet()效率更高一点。

为啥大数据量时,entrySet()效率高呢?

其实,keySet()遍历,其实是相当于遍历了两次,第一次是转换为Iterator对象,第二次才是根据key从Map中取出对应的value值。而entrySet()转成Entry对象,只遍历一次。
当然,还有其他的一些原因,比如,map.get(key),这一操作注定了是计算密集型操作,很耗费CPU,在此不再过多说明。

values()方式的说明

values(),顾名思义,它得到的是Map中value的集合,因此,想要获取value对应的key值比较困难,因此使用上还是看需求。

在日常的开发工作中推荐使用哪一种遍历方式呢?

直接说结论:推荐使用entrySet()遍历方式,这依然是不二之选。并不是很建议使用keySet方式。如果项目是JDK8以上的版本,直接使用forEach吧,底层原理一样,语法更好更简洁,何乐而不为呢?


三、写在最后

虽然本文比较基础,但对于初学者而言,依然是比较重要的一节课,毕竟这种数据结构的使用在日常的项目开发中,不可或缺。算是写了一篇水文,也算是延续继续更下去的习惯吧。

后续有时间可能会更一篇Java的数据结构,LIst、Map、Set等的底层原理以及JDK7、8版本底层的升级。

也有可能会研究一下Redis的使用。

谁又说的准呢?

毕竟下一次更文,还不知道呢。哈哈

我是小宗,Java学习,我和你一样,一直在路上。加油!

四、附录–全部测试源码

@Test
    void testMap() {
        //准备Map,装入测试数据
        Map<String, String> map = new HashMap<>();
        //往map里插入一百万数据,作为测试数据,别怕,家里就这条件
        for (int i = 0; i < 1000000; i++) {
            map.put(String.valueOf(i), "我是第" + i + "名");
        }

        //2.1 keySet()方式遍历-------for循环
        long keySetForStartTime = System.nanoTime();
        for (String key : map.keySet()) {
            map.get(key);
        }
        long keySetForEndTime = System.nanoTime();

        //2.2 keySet()方式遍历-------Iterator迭代
        long keySetIteratorStartTime = System.nanoTime();
        Iterator<String> iterator1 = map.keySet().iterator();
        while (iterator1.hasNext()) {
            String key = iterator1.next();
            map.get(key);
        }
        long keySetIteratorEndTime = System.nanoTime();


        //2.3 entrySet()方式遍历-------for循环
        long entrySetForStartTime = System.nanoTime();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            entry.getKey();
            entry.getValue();
        }
        long entrySetForEndTime = System.nanoTime();

        //2.4 entrySet()方式遍历-------Iterator迭代
        long entrySetIteratorStartTime = System.nanoTime();
        Iterator<Map.Entry<String, String>> iterator2 = map.entrySet().iterator();
        while (iterator2.hasNext()) {
            Map.Entry<String, String> entry = iterator2.next();
            entry.getKey();
            entry.getValue();
        }
        long entrySetIteratorEndTime = System.nanoTime();

        //2.5 values()方式遍历-------for循环
        long valuesForStartTime = System.nanoTime();
        Collection<String> values = map.values();
        for (String value : values) {
            //.....
        }
        long valuesForEndTime = System.nanoTime();

        //2.6 values()方式遍历-------Iterator迭代
        long valuesIteratorStartTime = System.nanoTime();
        Iterator<String> iterator3 = map.values().iterator();
        while (iterator3.hasNext()) {
            String value = iterator3.next();
        }
        long valuesIteratorEndTime = System.nanoTime();

        //2.7 JDK8-------Lambda表达式forEach遍历
        long forEachStartTime = System.nanoTime();
        map.forEach((key, value) -> {
            //......
        });
        long forEachEndTime = System.nanoTime();


        System.out.println("keySet()方式遍历-------for循环用时:" + (keySetForEndTime - keySetForStartTime) / 1000000 + "毫秒");
        System.out.println("keySet()方式遍历-------Iterator迭代用时:" + (keySetIteratorEndTime - keySetIteratorStartTime) / 1000000 + "毫秒");
        System.out.println("entrySet()方式遍历-------for循环用时:" + (entrySetForEndTime - entrySetForStartTime) / 1000000 + "毫秒");
        System.out.println("entrySet()方式遍历-------Iterator迭代用时:" + (entrySetIteratorEndTime - entrySetIteratorStartTime) / 1000000 + "毫秒");
        System.out.println("values()方式遍历-------for循环用时:" + (valuesForEndTime - valuesForStartTime) / 1000000 + "毫秒");
        System.out.println("values()方式遍历-------Iterator迭代用时:" + (valuesIteratorEndTime - valuesIteratorStartTime) / 1000000 + "毫秒");
        System.out.println("JDK8-------Lambda表达式forEach遍历用时:" + (forEachEndTime - forEachStartTime) / 1000000 + "毫秒");

    }
   

更多内容详见微信公众号:Python测试和开发

Python测试和开发

原文地址:https://www.cnblogs.com/phyger/p/14377609.html