HashMap是如何遍历的

前言

其实用上 JDK1.8 才是近些时日的事,毕竟没有什么新的技术点必须要去用,也懒得去换 JDK 的版本了。这几天在某论坛里看到一个有关于“HashMap如何遍历”的问题,静心一想也就知道那么一两种,于是想了想还是总结总结吧。

遍历方式

大概的总结了一下,HashMap 遍历就是分大概4个方向吧:

  1. 迭代器(Iterator)方式遍历。
  2. For Each 方式遍历。
  3. Lambda 表达式遍历(JDK1.8加入的)。
  4. Streams API 遍历(JDK1.8加入的)。

大概方向是这么几个,但是遍历方法也不少,一一举例吧。

有的朋友问我怎么我按顺序赋值怎么遍历出来顺序都不对啊,您是不是忘了一件事,Map里面是无序的O(∩_∩)O

(1)用 EntrySet 迭代器

package com.wlee.test;

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

public class Test {

	//定义一个Map
    public static Map<String, String> testMap = new HashMap() {{
        for (int i = 0; i < 10; i++) {
            put("key" + i, "val" + i);
        }
    }};

    public static void entrySetTest() {
        Iterator<Map.Entry<String, String>> iterator = testMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            System.out.println(entry.getKey() + " ------ " + entry.getValue());
        }
    }

    public static void main(String[] args) {
        Test.entrySetTest();
    }
}

(2)用 KeySet 迭代器

package com.wlee.test;

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

public class Test {

	//定义一个Map
    public static Map<String, String> testMap = new HashMap() {{
        for (int i = 0; i < 10; i++) {
            put("key" + i, "val" + i);
        }
    }};

	public static void keySetTest() {
        Iterator<String> iterator = testMap.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            System.out.println(key + " ------ " + testMap.get(key));
        }
    }

    public static void main(String[] args) {
        Test.keySetTest();
    }
}

(3)用 ForEach 处理 EntrySet

package com.wlee.test;

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

public class Test {

	//定义一个Map
    public static Map<String, String> testMap = new HashMap() {{
        for (int i = 0; i < 10; i++) {
            put("key" + i, "val" + i);
        }
    }};

    public static void forEach4EntrySetTest() {
        for (Map.Entry<String, String> entry : testMap.entrySet()) {
            System.out.println(entry.getKey() + " ------ " + entry.getValue());
        }
    }

    public static void main(String[] args) {
        Test.forEach4EntrySetTest();
    }
}

(4)用 ForEach 处理 KeySet

package com.wlee.test;

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

public class Test {

	//定义一个Map
    public static Map<String, String> testMap = new HashMap() {{
        for (int i = 0; i < 10; i++) {
            put("key" + i, "val" + i);
        }
    }};

    public static void forEach4KeySetTest() {
        for (String key : testMap.keySet()) {
            System.out.println(key + " ------ " + testMap.get(key));
        }
    }

    public static void main(String[] args) {
        Test.forEach4KeySetTest();
    }
}

(5)用 Lambda

package com.wlee.test;

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

public class Test {

	//定义一个Map
    public static Map<String, String> testMap = new HashMap() {{
        for (int i = 0; i < 10; i++) {
            put("key" + i, "val" + i);
        }
    }};

    public static void lambdaTest() {
        testMap.forEach((key, value) -> {
            System.out.println(key + " ------ " + value);
        });
    }

    public static void main(String[] args) {
        Test.lambdaTest();
    }
}

(6)用 StreamsAPI 的单线程

package com.wlee.test;

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

public class Test {

	//定义一个Map
    public static Map<String, String> testMap = new HashMap() {{
        for (int i = 0; i < 10; i++) {
            put("key" + i, "val" + i);
        }
    }};

    public static void streamTest() {
        testMap.entrySet().stream().forEach((entry) -> {
            System.out.println(entry.getKey() + " ------ " + entry.getValue());
        });
    }

    public static void main(String[] args) {
        Test.streamTest();
    }
}

(7)用 StreamsAPI 的多线程

package com.wlee.test;

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

public class Test {

	//定义一个Map
    public static Map<String, String> testMap = new HashMap() {{
        for (int i = 0; i < 10; i++) {
            put("key" + i, "val" + i);
        }
    }};

    public static void parallelStreamTest() {
        testMap.entrySet().parallelStream().forEach((entry) -> {
            System.out.println(entry.getKey() + " ------ " + entry.getValue());
        });
    }

    public static void main(String[] args) {
        Test.parallelStreamTest();
    }
}

大概是总结了7种遍历方式,当然可能不一定就只有这7种,还需要朋友们多多指教。有时候您会问这些方式都哪些方式更快,性能更好。其实各种遍历方式差别不是很大。

番外篇(性能测试)

Maven 项目请先引入:

<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.20</version>
    <scope>provided</scope>
</dependency>

然后测试类:

package com.wlee.test;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

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

@BenchmarkMode(Mode.Throughput) //测试类型:吞吐量
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) //预热2轮,每次1s
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) //测试5轮,每次3s
@Fork(1) //fork1个线程
@State(Scope.Thread) //每个测试线程一个实例
public class HashMapTest {

    public static Map<String, String> testMap = new HashMap() {{
        for (int i = 0; i < 10; i++) {
            put("key" + i, "val" + i);
        }
    }};

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(HashMapTest.class.getSimpleName()) // 要导入的测试类
                .output("d:/test/map_test.log") // 输出测试结果的文件
                .build();
        new Runner(opt).run(); // 执行测试
    }

    @Benchmark
    public static void entrySetTest() {
        Iterator<Map.Entry<String, String>> iterator = testMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            System.out.println(entry.getKey() + " ------ " + entry.getValue());
        }
    }

    @Benchmark
    public static void keySetTest() {
        Iterator<String> iterator = testMap.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            System.out.println(key + " ------ " + testMap.get(key));
        }
    }

    @Benchmark
    public static void forEach4EntrySetTest() {
        for (Map.Entry<String, String> entry : testMap.entrySet()) {
            System.out.println(entry.getKey() + " ------ " + entry.getValue());
        }
    }

    @Benchmark
    public static void forEach4KeySetTest() {
        for (String key : testMap.keySet()) {
            System.out.println(key + " ------ " + testMap.get(key));
        }
    }

    @Benchmark
    public static void lambdaTest() {
        testMap.forEach((key, value) -> {
            System.out.println(key + " ------ " + value);
        });
    }

    @Benchmark
    public static void streamTest() {
        testMap.entrySet().stream().forEach((entry) -> {
            System.out.println(entry.getKey() + " ------ " + entry.getValue());
        });
    }

    @Benchmark
    public static void parallelStreamTest() {
        testMap.entrySet().parallelStream().forEach((entry) -> {
            System.out.println(entry.getKey() + " ------ " + entry.getValue());
        });
    }
}

JMH(Java Microbenchmark Harness,JAVA 微基准测试套件)是 Oracle 官方提供的性能测试工具。

测试结束从 log 日志文件中获取相关的信息片段:

Benchmark                          Mode  Cnt  Score   Error   Units
HashMapTest.entrySetTest          thrpt    5  2.725 ± 0.319  ops/ms
HashMapTest.forEach4EntrySetTest  thrpt    5  2.947 ± 0.416  ops/ms
HashMapTest.forEach4KeySetTest    thrpt    5  2.914 ± 0.701  ops/ms
HashMapTest.keySetTest            thrpt    5  2.799 ± 0.294  ops/ms
HashMapTest.lambdaTest            thrpt    5  2.850 ± 0.455  ops/ms
HashMapTest.parallelStreamTest    thrpt    5  2.420 ± 0.581  ops/ms
HashMapTest.streamTest            thrpt    5  2.811 ± 0.390  ops/ms

其中 Score 列表示平均执行时间, ± 符号表示误差。从测试结果可以看出有快有慢,其实各种遍历方法在性能方面差别不是很大。

以上测试可能存在误差,毕竟每天机器的配置环境什么都不太一样,仅供参考

结语

其实以上主要是为了分享几种遍历 HashMap 的方式,具体性能测试,甚至安全测试不是主要内容。而且测试代码作者也是参考网络上的文章。


作者:WorkerLee
链接:https://juejin.im/post/5eea2040f265da02a224818f
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章