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
 

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