Seven traversal methods and performance analysis of HashMap, a must for high salary

Posted by 990805 on Mon, 04 May 2020 14:53:42 +0200

With the release of JDK 1.8 Streams API, HashMap has more ways to traverse, but which one should be chosen? Instead, it becomes a problem.

This paper starts with the traversal method of HashMap, and then analyzes the advantages and disadvantages of various traversal methods of HashMap from the aspects of performance, principle and security. The main contents of this paper are as follows:

 

 

Also note here: no matter you are for Java high salary or hobbies, remember: project development experience is always the core. If you don't have the latest Java architecture practical video tutorial and the interview book of large factory, you can go to the small-scale Java architecture to learn. Skirt: seven umbrella bar zero, but no umbrella (Digital homophony) you can find it under conversion. There are many new Java architecture project tutorials in it. You can also communicate with the old driver for advice!  

HashMap traversal

In terms of large direction, HashMap traversal can be divided into the following four categories:

  1. Iterator mode traversal;
  2. Traverse in For Each mode;
  3. Lambda expression traversal (JDK 1.8 +);
  4. Streams API traversal (JDK 1.8 +).

However, there are different implementation methods under each type, so the specific traversal methods can be divided into the following 7 types:

  1. Iterator EntrySet is used for traversal;
  2. Iterator KeySet is used for traversal;
  3. Traverse in the way of For Each EntrySet;
  4. Traverse in the way of For Each KeySet;
  5. Use Lambda expression to traverse;
  6. Using Streams API to traverse in a single thread way;
  7. Traversal is done in a multi-threaded manner using the Streams API.

Next, let's look at the specific implementation code of each traversal method.

1. Iterator EntrySet

public class HashMapTest {
    public static void main(String[] args) {
        // Create and assign a HashMap
        Map<Integer, String> map = new HashMap();
        map.put(1, "Java");
        map.put(2, "JDK");
        map.put(3, "Spring Framework");
        map.put(4, "MyBatis framework");
        map.put(5, "Java Chinese community");
        // ergodic
        Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, String> entry = iterator.next();
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
    }
}
Copy code

The execution results of the above procedures are as follows:

1

Java

2

JDK

3

Spring Framework

4

MyBatis framework

5

Java Chinese community

2. Iterator KeySet

public class HashMapTest {
    public static void main(String[] args) {
        // Create and assign a HashMap
        Map<Integer, String> map = new HashMap();
        map.put(1, "Java");
        map.put(2, "JDK");
        map.put(3, "Spring Framework");
        map.put(4, "MyBatis framework");
        map.put(5, "Java Chinese community");
        // ergodic
        Iterator<Integer> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            Integer key = iterator.next();
            System.out.println(key);
            System.out.println(map.get(key));
        }
    }
}
Copy code

The execution results of the above procedures are as follows:

1

Java

2

JDK

3

Spring Framework

4

MyBatis framework

5

Java Chinese community

3.ForEach EntrySet

public class HashMapTest {
    public static void main(String[] args) {
        // Create and assign a HashMap
        Map<Integer, String> map = new HashMap();
        map.put(1, "Java");
        map.put(2, "JDK");
        map.put(3, "Spring Framework");
        map.put(4, "MyBatis framework");
        map.put(5, "Java Chinese community");
        // ergodic
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
    }
}
Copy code

The execution results of the above procedures are as follows:

1

Java

2

JDK

3

Spring Framework

4

MyBatis framework

5

Java Chinese community

4.ForEach KeySet

public class HashMapTest {
    public static void main(String[] args) {
        // Create and assign a HashMap
        Map<Integer, String> map = new HashMap();
        map.put(1, "Java");
        map.put(2, "JDK");
        map.put(3, "Spring Framework");
        map.put(4, "MyBatis framework");
        map.put(5, "Java Chinese community");
        // ergodic
        for (Integer key : map.keySet()) {
            System.out.println(key);
            System.out.println(map.get(key));
        }
    }
}
Copy code

The execution results of the above procedures are as follows:

1

Java

2

JDK

3

Spring Framework

4

MyBatis framework

5

Java Chinese community

5.Lambda

public class HashMapTest {
    public static void main(String[] args) {
        // Create and assign a HashMap
        Map<Integer, String> map = new HashMap();
        map.put(1, "Java");
        map.put(2, "JDK");
        map.put(3, "Spring Framework");
        map.put(4, "MyBatis framework");
        map.put(5, "Java Chinese community");
        // ergodic
        map.forEach((key, value) -> {
            System.out.println(key);
            System.out.println(value);
        });
    }
}
Copy code

The execution results of the above procedures are as follows:

1

Java

2

JDK

3

Spring Framework

4

MyBatis framework

5

Java Chinese community

6.Streams API single thread

public class HashMapTest {
    public static void main(String[] args) {
        // Create and assign a HashMap
        Map<Integer, String> map = new HashMap();
        map.put(1, "Java");
        map.put(2, "JDK");
        map.put(3, "Spring Framework");
        map.put(4, "MyBatis framework");
        map.put(5, "Java Chinese community");
        // ergodic
        map.entrySet().stream().forEach((entry) -> {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        });
    }
}
Copy code

The execution results of the above procedures are as follows:

1

Java

2

JDK

3

Spring Framework

4

MyBatis framework

5

Java Chinese community

7.Streams API multithreading

public class HashMapTest {
    public static void main(String[] args) {
        // Create and assign a HashMap
        Map<Integer, String> map = new HashMap();
        map.put(1, "Java");
        map.put(2, "JDK");
        map.put(3, "Spring Framework");
        map.put(4, "MyBatis framework");
        map.put(5, "Java Chinese community");
        // ergodic
        map.entrySet().parallelStream().forEach((entry) -> {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        });
    }
}
Copy code

The execution results of the above procedures are as follows:

4

MyBatis framework

5

Java Chinese community

1

Java

2

JDK

3

Spring Framework

performance testing

Next, we use the official performance testing tool JMH (Java Microbenchmark Harness) provided by Oracle to test the performance of these seven loops.

First, we need to introduce the JMH framework, and add the following configuration in the pom.xml file:

<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.23</version>
    <scope>provided</scope>
</dependency>
Copy code

Then write the test code as follows:

@BenchmarkMode(Mode.AverageTime) // Test completion time
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // Preheat 2 rounds for 1s each time
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // Test 5 rounds, 3 s each time
@Fork(1) // fork 1 thread
@State(Scope.Thread) // One instance per test thread
public class HashMapCycleTest {
    static Map<Integer, String> map = new HashMap() {{
        // Add data
        for (int i = 0; i < 100; i++) {
            put(i, "val:" + i);
        }
    }};

    public static void main(String[] args) throws RunnerException {
        // Start benchmark
        Options opt = new OptionsBuilder()
                .include(HashMapCycle.class.getSimpleName()) // Test class to import
                .output("/Users/admin/Desktop/jmh-map.log") // Output test results file
                .build();
        new Runner(opt).run(); // Perform tests
    }

    @Benchmark
    public void entrySet() {
        // ergodic
        Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, String> entry = iterator.next();
            Integer k = entry.getKey();
            String v = entry.getValue();
        }
    }

    @Benchmark
    public void forEachEntrySet() {
        // ergodic
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            Integer k = entry.getKey();
            String v = entry.getValue();
        }
    }

    @Benchmark
    public void keySet() {
        // ergodic
        Iterator<Integer> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            Integer k = iterator.next();
            String v = map.get(k);
        }
    }

    @Benchmark
    public void forEachKeySet() {
        // ergodic
        for (Integer key : map.keySet()) {
            Integer k = key;
            String v = map.get(k);
        }
    }

    @Benchmark
    public void lambda() {
        // ergodic
        map.forEach((key, value) -> {
            Integer k = key;
            String v = map.get(k);
        });
    }

    @Benchmark
    public void streamApi() {
        // Single thread traversal
        map.entrySet().stream().forEach((entry) -> {
            Integer k = entry.getKey();
            String v = entry.getValue();
        });
    }

    public void parallelStreamApi() {
        // Multithreading traversal
        map.entrySet().parallelStream().forEach((entry) -> {
            Integer k = entry.getKey();
            String v = entry.getValue();
        });
    }
}
Copy code

All the methods added with @ Benchmark annotation will be tested. Because parallel stream is the best multi-threaded version, it will not participate in the test. The test results of the other six methods are as follows:

 

 

Where the Score column represents the average execution time and the ± symbol represents the error. From the above results, we can see that the performance of the two entrysets is similar, and the execution speed is the fastest. Next is stream, then two keysets. The worst performance is lambda expression.

Note: the above results are based on the test environment: JDK 1.8 / Mac mini (2018) / Idea 2020.1

Summary

So we should try to use iterator or entrySet traversal of for loop, or stream.

Performance principle analysis

To understand the results of performance test, we need to compile all traversal codes into bytecode through javac to see the specific reasons. After compilation, we use Idea to open bytecode information, as follows:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.example;

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

public class HashMapTest {
    static Map<Integer, String> map = new HashMap() {
        {
            for(int var1 = 0; var1 < 2; ++var1) {
                this.put(var1, "val:" + var1);
            }

        }
    };

    public HashMapTest() {
    }

    public static void main(String[] var0) {
        entrySet();
        keySet();
        forEachEntrySet();
        forEachKeySet();
        lambda();
        streamApi();
        parallelStreamApi();
    }

    public static void entrySet() {
        Iterator var0 = map.entrySet().iterator();

        while(var0.hasNext()) {
            Entry var1 = (Entry)var0.next();
            System.out.println(var1.getKey());
            System.out.println((String)var1.getValue());
        }

    }

    public static void keySet() {
        Iterator var0 = map.keySet().iterator();

        while(var0.hasNext()) {
            Integer var1 = (Integer)var0.next();
            System.out.println(var1);
            System.out.println((String)map.get(var1));
        }

    }

    public static void forEachEntrySet() {
        Iterator var0 = map.entrySet().iterator();

        while(var0.hasNext()) {
            Entry var1 = (Entry)var0.next();
            System.out.println(var1.getKey());
            System.out.println((String)var1.getValue());
        }

    }

    public static void forEachKeySet() {
        Iterator var0 = map.keySet().iterator();

        while(var0.hasNext()) {
            Integer var1 = (Integer)var0.next();
            System.out.println(var1);
            System.out.println((String)map.get(var1));
        }

    }

    public static void lambda() {
        map.forEach((var0, var1) -> {
            System.out.println(var0);
            System.out.println(var1);
        });
    }

    public static void streamApi() {
        map.entrySet().stream().forEach((var0) -> {
            System.out.println(var0.getKey());
            System.out.println((String)var0.getValue());
        });
    }

    public static void parallelStreamApi() {
        map.entrySet().parallelStream().forEach((var0) -> {
            System.out.println(var0.getKey());
            System.out.println((String)var0.getValue());
        });
    }
}

Copy code

It can be seen from the results that, in addition to the Lambda and Streams API, the code generated by the EntrySet traversed by the iterator loop and the for loop is the same. They all create a traversal object Entry in the loop, as shown below:

public static void entrySet() {
    Iterator var0 = map.entrySet().iterator();
    while(var0.hasNext()) {
        Entry var1 = (Entry)var0.next();
        System.out.println(var1.getKey());
        System.out.println((String)var1.getValue());
    }
}
public static void forEachEntrySet() {
    Iterator var0 = map.entrySet().iterator();
    while(var0.hasNext()) {
        Entry var1 = (Entry)var0.next();
        System.out.println(var1.getKey());
        System.out.println((String)var1.getValue());
    }
}
Copy code

The KeySet code traversed by iterator and for loop is the same, as follows:

public static void keySet() {
    Iterator var0 = map.keySet().iterator();
    while(var0.hasNext()) {
        Integer var1 = (Integer)var0.next();
        System.out.println(var1);
        System.out.println((String)map.get(var1));
    }
} 
public static void forEachKeySet() {
    Iterator var0 = map.keySet().iterator();
    while(var0.hasNext()) {
        Integer var1 = (Integer)var0.next();
        System.out.println(var1);
        System.out.println((String)map.get(var1));
    }
}
Copy code

You can see that KeySet creates an Integer local variable in the loop, and the value is obtained directly from the map object.

Therefore, from the above test results, we can see that the performance of the two methods of the final test result, EntrySet, is similar, and KeySet is the same.

Security test

From the above performance test results and principle analysis, I think you should choose that traversal method, which is already well known. Next, we will start from the perspective of "security" to analyze that traversal method is more secure.

We divide the above traversal into four types For testing: iterator mode, For loop mode, Lambda mode and Stream mode. The test code is as follows.

1. Iterator mode

Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<Integer, String> entry = iterator.next();
    if (entry.getKey() == 1) {
        // delete
        System.out.println("del:" + entry.getKey());
        iterator.remove();
    } else {
        System.out.println("show:" + entry.getKey());
    }
}
Copy code

Results of the above procedures:

show:0

del:1

show:2

Test result: loop delete data security in iterator.

2.For cycle mode

for (Map.Entry<Integer, String> entry : map.entrySet()) {
    if (entry.getKey() == 1) {
        // delete
        System.out.println("del:" + entry.getKey());
        map.remove(entry.getKey());
    } else {
        System.out.println("show:" + entry.getKey());
    }
}
Copy code

Results of the above procedures:

 

 

Test result: it is not safe to delete data in the For loop.

3.Lambda mode

map.forEach((key, value) -> {
    if (key == 1) {
        System.out.println("del:" + key);
        map.remove(key);
    } else {
        System.out.println("show:" + key);
    }
});
Copy code

Results of the above procedures:

 

Test result: deleting data in Lambda loop is not safe.

 

Correct way to delete Lambda:

// Delete according to the key in the map
map.keySet().removeIf(key -> key == 1);
map.forEach((key, value) -> {
    System.out.println("show:" + key);
});
Copy code

Results of the above procedures:

show:0

show:2

As can be seen from the above code, it is a correct way to operate the collection by first using Lambda's removeIf to delete redundant data and then looping.

4.Stream mode

map.entrySet().stream().forEach((entry) -> {
    if (entry.getKey() == 1) {
        System.out.println("del:" + entry.getKey());
        map.remove(entry.getKey());
    } else {
        System.out.println("show:" + entry.getKey());
    }
});
Copy code

Results of the above procedures:

 

 

Test result: it is not safe to delete data in Stream loop.

The correct way to loop a Stream:

map.entrySet().stream().filter(m -> 1 != m.getKey()).forEach((entry) -> {
    if (entry.getKey() == 1) {
        System.out.println("del:" + entry.getKey());
    } else {
        System.out.println("show:" + entry.getKey());
    }
});
Copy code

Results of the above procedures:

show:0

show:2

From the above code, we can see that it is a safe way to use the filter in Stream to filter out the useless data, and then traverse it.

Summary

We can't use the set map.remove() to delete data in traversal, which is an unsafe operation, but we can use iterator.remove() of iterator to delete data, which is a safe way to delete collection. Similarly, we can use removeIf in Lambda to delete data in advance, or use filter in Stream to filter out the data to be deleted for looping, which is safe. Of course, we can also delete data before the for loop, which is thread safe.

summary
Note: no matter you are for Java high salary or hobbies, remember: project development experience is always the core. If you don't have the latest Java architecture practical video tutorial and the interview book of large factory, you can go to the small-scale Java architecture to learn. Skirt: seven umbrella bar, zero clothes and zero umbrella (Digital homophony) you can find it under conversion. There are many new Java architecture project tutorials in it. You can also communicate with the old driver for advice!  

In this paper, we talk about four traversal methods of HashMap: iterator, for, lambda, stream, and seven specific traversal methods. In terms of comprehensive performance and security, we should try to use the traversal method of iterator entrySet or stream filter to traverse HashMap.

The text and pictures of this article come from the Internet and my own ideas. They are only for learning and communication. They have no commercial use. The copyright belongs to the original author. If you have any questions, please contact us in time for handling

Topics: Java JDK Lambda Spring