2024-03-14
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1951787424

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

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

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

最后

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