四时宝库

程序员的知识宝库

HashMap 七种遍历策略:性能对比与分析

引言

在Java编程中,HashMap是一种常用的键值对数据结构,它提供了高效的查找、插入和删除操作。然而,当涉及到遍历大量数据时,不同的遍历方法对性能的影响不可忽视。本文将通过实验数据,对比分析七种遍历HashMap的方法,并探讨各自的优缺点。

方法与数据

在实验中,我们创建了一个包含500万条记录的HashMap,并使用Spring框架的StopWatch工具来测量每种遍历方法的耗时。以下是所测试的遍历方法及其平均执行时间(单位:纳秒):

  1. 使用迭代器遍历EntrySet - 平均耗时:约37毫秒
  2. 使用迭代器遍历KeySet - 平均耗时:约42毫秒
  3. 使用For Each遍历EntrySet - 平均耗时:约37毫秒
  4. 使用For Each遍历KeySet - 平均耗时:约42毫秒
  5. 使用Lambda表达式遍历 - 平均耗时:约57毫秒
  6. 使用Streams API单线程遍历 - 平均耗时:约39毫秒
  7. 使用Streams API多线程遍历 - 平均耗时:约24毫秒

分析与比较

  • 迭代器遍历EntrySetFor Each遍历EntrySet: 这两种方法在性能上相近,因为它们都直接访问了HashMap的entrySet(),避免了额外的get()调用。选择哪种取决于个人编码习惯,但性能差异微乎其微。
  • 迭代器遍历KeySetFor Each遍历KeySet: 这两种方法的耗时略高,原因是每次循环都需要调用map.get(key)来获取值,这增加了额外的查找开销。
  • 使用Lambda表达式遍历: Lambda表达式的耗时最高,这主要是因为Lambda引入了额外的函数调用开销,尽管代码更加简洁,但在大规模数据集上效率较低。
  • 使用Streams API单线程遍历: Streams API提供了更现代的编程模型,允许使用函数式风格的代码。然而,单线程流并未显著提高性能,反而由于流的创建和处理增加了开销。
  • 使用Streams API多线程遍历: 当利用多线程时,Streams API展现出最佳性能,这是因为并行处理可以有效分摊计算负载,尤其在多核处理器上效果明显。但需要注意的是,多线程遍历可能引入线程安全问题,特别是在修改HashMap的情况下。

结论

在遍历HashMap时,如果追求性能,建议使用迭代器遍历EntrySetFor Each遍历EntrySet,它们在大多数情况下提供了最佳平衡。而当处理数据集特别庞大且运行环境支持多线程时,使用Streams API多线程遍历则是最优选择,能够充分利用现代处理器的并行处理能力。不过,在选择遍历策略时,还需综合考虑代码可读性和维护性,以及具体的业务需求。

测试源码

import org.springframework.util.StopWatch;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author CQY
 * @version 1.0
 * @date 2024/7/18 14:29
 **/
public class TestHashMap {

    public static void main(String[] args) {

        // 创建一个 HashMap,写入500W条记录
        HashMap<Integer, Object> map = new HashMap<>();
        for (int i = 0; i < 500_0000; i++) {
            map.put(i, "value" + i);
        }

        StopWatch stopWatch = new StopWatch("TestHashMap");

        stopWatch.start("1.使用迭代器 EntrySet 的方式遍历");
        Iterator<Map.Entry<Integer, Object>> iterator1 = map.entrySet().iterator();
        while (iterator1.hasNext()) {
            Map.Entry<Integer, Object> next = iterator1.next();
            Integer key = next.getKey();
            Object value = next.getValue();
        }
        stopWatch.stop();

        stopWatch.start("2.使用迭代器的KeySet");
        Iterator<Integer> iterator2 = map.keySet().iterator();
        while (iterator2.hasNext()) {
            Integer key = iterator2.next();
            Object value = map.get(key);
        }
        stopWatch.stop();

        stopWatch.start("3.使用 For Each EntrySet 的方式进行遍历");
        for (Map.Entry<Integer, Object> entry : map.entrySet()) {
            Integer key = entry.getKey();
            Object value = entry.getValue();
        }
        stopWatch.stop();


        stopWatch.start("4.使用 For Each KeySet 的方式进行遍历");
        for (Integer key : map.keySet()) {
            // key
            Object value = map.get(key);
        }
        stopWatch.stop();

        stopWatch.start("5.使用 Lambda 表达式的方式进行遍历");
        map.forEach((key, value) -> {
            // key  value
        });
        stopWatch.stop();

        stopWatch.start("6.使用 Streams API 单线程的方式进行遍历");
        map.entrySet().stream().forEach((integerStringEntry -> {
            Integer key = integerStringEntry.getKey();
            Object value = integerStringEntry.getValue();

        }));
        stopWatch.stop();


        stopWatch.start("7.使用 Streams API 多线程的方式进行遍历");
        map.entrySet().parallelStream().forEach((integerStringEntry -> {
            Integer key = integerStringEntry.getKey();
            Object value = integerStringEntry.getValue();
        }));
        stopWatch.stop();

        System.out.println(stopWatch.prettyPrint());

    }
}

StopWatch耗时打印

StopWatch 'TestHashMap': running time = 279937200 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
036545100  013%  1.使用迭代器 EntrySet 的方式遍历
042892700  015%  2.使用迭代器的KeySet
037677900  013%  3.使用 For Each EntrySet 的方式进行遍历
042370800  015%  4.使用 For Each KeySet 的方式进行遍历
057247300  020%  5.使用 Lambda 表达式的方式进行遍历
039540800  014%  6.使用 Streams API 单线程的方式进行遍历
023662600  008%  7.使用 Streams API 多线程的方式进行遍历


StopWatch 'TestHashMap': running time = 276661500 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
035001300  013%  1.使用迭代器 EntrySet 的方式遍历
043237200  016%  2.使用迭代器的KeySet
037038400  013%  3.使用 For Each EntrySet 的方式进行遍历
043772700  016%  4.使用 For Each KeySet 的方式进行遍历
057677600  021%  5.使用 Lambda 表达式的方式进行遍历
036356600  013%  6.使用 Streams API 单线程的方式进行遍历
023577700  009%  7.使用 Streams API 多线程的方式进行遍历



StopWatch 'TestHashMap': running time = 318431400 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
044152200  014%  1.使用迭代器 EntrySet 的方式遍历
051133000  016%  2.使用迭代器的KeySet
047733600  015%  3.使用 For Each EntrySet 的方式进行遍历
051962300  016%  4.使用 For Each KeySet 的方式进行遍历
058811800  018%  5.使用 Lambda 表达式的方式进行遍历
036561500  011%  6.使用 Streams API 单线程的方式进行遍历
028077000  009%  7.使用 Streams API 多线程的方式进行遍历

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接