HashMap 的迭代方式有哪几种?

 作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题


代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等


联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等

释放21集全网最深ConcurrentHashMap的vip视频,复现每一行源码

HashMap 的迭代方式一般有如下几种:

  • 使用 for-each 循环遍历 keySet()
  • 使用 for-each 循环遍历 entrySet()
  • 使用 Iterator 遍历 keySet()
  • 使用 Iterator 遍历 entrySet()
  • 使用 Lambda 表达式
  • 使用 Streams API 单线程
  • 使用 Streams API 多线程

详情

基础数据

    private Map<Integer,String> hashMap = new HashMap<>(){{
        put(1,"死磕");
        put(2,"死磕 Java");
        put(3,"死磕 Spring");
        put(4,"死磕 Redis");
        put(5,"死磕 Java NIO");
        put(6,"死磕 Netty");
        put(7,"死磕 Java 新特性");
        put(8,"死磕 Java 基础");
    }};

使用 for-each 循环遍历 keySet()

    @Test
    public void test1() {
        for (Integer key : hashMap.keySet()) {
            System.out.println(key + "---" + hashMap.get(key));
        }
    }

执行结果:

1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础

使用 for-each 循环遍历 entrySet()

    @Test
    public void test2() {
        for (Map.Entry<Integer,String> entry : hashMap.entrySet()) {
            System.out.println(entry.getKey() + "---" + entry.getValue());
        }
    }

执行结果:

1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础

使用 Iterator 遍历 keySet()

    @Test
    public void test4() {
        Iterator<Integer> iterator =  hashMap.keySet().iterator();
        while (iterator.hasNext()) {
            Integer key = iterator.next();
            System.out.println(key + "---" + hashMap.get(key));
        }
    }

执行结果:

1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础

使用 Iterator 遍历 entrySet()

    @Test
    public void test3() {
        Iterator<Map.Entry<Integer,String>> iterator =  hashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer,String> entry = iterator.next();
            System.out.println(entry.getKey() + "---" + entry.getValue());
        }
    }

执行结果:

1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础

使用 Lambda 表达式

    @Test
    public void test5() {
        hashMap.forEach((key,value) -> {
            System.out.println(key + "---" + hashMap.get(key));
        });
    }

执行结果:

1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础

使用 Streams API 单线程

    @Test
    public void test6() {
        hashMap.entrySet().stream().forEach((entry) -> {
            System.out.println(entry.getKey() + "---" + entry.getValue());
        });
    }

执行结果:

1---死磕
2---死磕 Java
3---死磕 Spring
4---死磕 Redis
5---死磕 Java NIO
6---死磕 Netty
7---死磕 Java 新特性
8---死磕 Java 基础

使用 Streams API 多线程

    @Test
    public void test7() {
        hashMap.entrySet().parallelStream().forEach((entry) -> {
            System.out.println(entry.getKey() + "---" + entry.getValue());
        });
    }

执行结果:

    @Test
    public void test7() {
        hashMap.entrySet().parallelStream().forEach((entry) -> {
            System.out.println(entry.getKey() + "---" + entry.getValue());
        });
    }

扩展

性能测试

下面我们来测试这七种方法的性能,使用性能测试专用工具 JMH(不要再用 System.currentTimeMillis() 了)。

先添加依赖:

        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>1.23</version>
        </dependency>

        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>1.23</version>
            <scope>provided</scope>
        </dependency>

再写测试代码:

@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 1s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class HashMapTest {
    static Map<Integer,String> hashMap = new HashMap<>(){{
        for (int i = 0 ; i < 10000 ; i++) {
            put(i,"sike-" + i);
        }
    }};

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(HashMapTest.class.getSimpleName())
                .output("/Users/chenssy/Downloads/jmh-hashMap.log")
                .build();
        new Runner(opt).run(); // 执行测试
    }

    @Benchmark
    public void keySet() {
        for (Integer key : hashMap.keySet()) {
            Integer key1 = key;
            String value1 = hashMap.get(key1);
        }
    }

    @Benchmark
    public void entrySet() {
        for (Map.Entry<Integer,String> entry : hashMap.entrySet()) {
            Integer key = entry.getKey();
            String value1 = entry.getValue();
        }
    }

    @Benchmark
    public void entrySetIterator() {
        Iterator<Map.Entry<Integer,String>> iterator =  hashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer,String> entry = iterator.next();
            Integer key = entry.getKey();
            String value = entry.getValue();
        }
    }

    @Benchmark
    public void keySetIterator() {
        Iterator<Integer> iterator =  hashMap.keySet().iterator();
        while (iterator.hasNext()) {
            Integer key = iterator.next();
            String value = hashMap.get(key);
        }
    }

    @Benchmark
    public void lambda() {
        hashMap.forEach((key,value) -> {
            Integer key1 = key;
            String value1 = value;
        });
    }

    @Benchmark
    public void stream() {
        hashMap.entrySet().stream().forEach((entry) -> {
            Integer key = entry.getKey();
            String value = entry.getValue();
        });
    }
}

我们分别验证 100、1000、10000 个元素的测试结果:

  • 100
Benchmark                     Mode  Cnt    Score   Error  Units
HashMapTest.entrySet          avgt    5  362.568 ± 8.211  ns/op
HashMapTest.entrySetIterator  avgt    5  361.015 ± 5.712  ns/op
HashMapTest.keySet            avgt    5  749.805 ± 2.336  ns/op
HashMapTest.keySetIterator    avgt    5  752.219 ± 3.765  ns/op
HashMapTest.lambda            avgt    5  265.254 ± 1.614  ns/op
HashMapTest.stream            avgt    5  375.628 ± 2.425  ns/op
  • 1000
Benchmark                     Mode  Cnt     Score     Error  Units
HashMapTest.entrySet          avgt    5  3211.032 ± 139.991  ns/op
HashMapTest.entrySetIterator  avgt    5  3323.444 ±  17.326  ns/op
HashMapTest.keySet            avgt    5  7164.590 ± 109.561  ns/op
HashMapTest.keySetIterator    avgt    5  7204.525 ± 100.791  ns/op
HashMapTest.lambda            avgt    5  2458.673 ±  81.381  ns/op
HashMapTest.stream            avgt    5  3992.651 ±  41.205  ns/op
  • 10000
Benchmark                     Mode  Cnt      Score       Error  Units
HashMapTest.entrySet          avgt    5  35797.452 ±  5896.667  ns/op
HashMapTest.entrySetIterator  avgt    5  35611.409 ±  6153.680  ns/op
HashMapTest.keySet            avgt    5  57320.843 ± 42999.041  ns/op
HashMapTest.keySetIterator    avgt    5  57748.329 ± 40455.889  ns/op
HashMapTest.lambda            avgt    5  28654.233 ±   598.318  ns/op
HashMapTest.stream            avgt    5  37840.969 ±  3215.428  ns/op

从测试结果上面来看 entrySet 和 Lambda 表达式两个性能最佳。keySet 的性能最差,主要原因是 keySet 获取 value 的时候还需要调用 HashMap 的 get() 查询。

每个人会因为使用机器、环境、JVM 版本的不同而导致测试的结果不同。

最后

  • 对于大多数场景,使用entrySet()遍历是最最佳的选择。
  • 如果追求代码简洁,推荐使用 Lambda 表达式。
  • 如果需要在迭代时修改 HashMap ,推荐使用迭代器 entrySet() 的方式。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值