Fastest & Safest HashMap Traversal: Compare Java 8 Streams, Lambdas, and Iterators

This article examines the four major HashMap traversal techniques—Iterator, for‑each loops, Lambda expressions, and Streams API—detailing seven concrete implementations, benchmarking their performance with JMH, analyzing generated bytecode, and evaluating safety when modifying collections during iteration, ultimately recommending the most efficient and secure approaches.

macrozheng
macrozheng
macrozheng
Fastest & Safest HashMap Traversal: Compare Java 8 Streams, Lambdas, and Iterators

With the release of JDK 1.8 Streams API, HashMap now has more traversal options, but choosing the right one can be confusing.

The article first introduces HashMap traversal methods, then analyzes their performance, principles, and safety.

HashMap Traversal

HashMap traversal can be divided into four high‑level categories:

Iterator (Iterator) traversal

For‑Each traversal

Lambda expression traversal (JDK 1.8+)

Streams API traversal (JDK 1.8+)

Each category has specific implementations, resulting in seven concrete ways:

Iterator EntrySet

Iterator KeySet

For‑Each EntrySet

For‑Each KeySet

Lambda expression

Streams API single‑thread

Streams API multi‑thread

1. Iterator EntrySet

public class HashMapTest {
    public static void main(String[] args) {
        // create and populate 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中文社群");
        // traversal
        Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, String> entry = iterator.next();
            System.out.print(entry.getKey());
            System.out.print(entry.getValue());
        }
    }
}
1JavaJDK3Spring Framework4MyBatis framework5Java中文社群

2. Iterator KeySet

public class HashMapTest {
    public static void main(String[] args) {
        // create and populate 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中文社群");
        // traversal
        Iterator<Integer> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            Integer key = iterator.next();
            System.out.print(key);
            System.out.print(map.get(key));
        }
    }
}
1Java2JDK3Spring Framework4MyBatis framework5Java中文社群

3. For‑Each EntrySet

public class HashMapTest {
    public static void main(String[] args) {
        // create and populate 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中文社群");
        // traversal
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.print(entry.getKey());
            System.out.print(entry.getValue());
        }
    }
}
1Java2JDK3Spring Framework4MyBatis framework5Java中文社群

4. For‑Each KeySet

public class HashMapTest {
    public static void main(String[] args) {
        // create and populate 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中文社群");
        // traversal
        for (Integer key : map.keySet()) {
            System.out.print(key);
            System.out.print(map.get(key));
        }
    }
}
1Java2JDK3Spring Framework4MyBatis framework5Java中文社群

5. Lambda

public class HashMapTest {
    public static void main(String[] args) {
        // create and populate 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中文社群");
        // traversal
        map.forEach((key, value) -> {
            System.out.print(key);
            System.out.print(value);
        });
    }
}
1Java2JDK3Spring Framework4MyBatis framework5Java中文社群

6. Streams API Single‑Thread

public class HashMapTest {
    public static void main(String[] args) {
        // create and populate 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中文社群");
        // traversal
        map.entrySet().stream().forEach((entry) -> {
            System.out.print(entry.getKey());
            System.out.print(entry.getValue());
        });
    }
}
1Java2JDK3Spring Framework4MyBatis framework5Java中文社群

7. Streams API Multi‑Thread

public class HashMapTest {
    public static void main(String[] args) {
        // create and populate 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中文社群");
        // traversal
        map.entrySet().parallelStream().forEach((entry) -> {
            System.out.print(entry.getKey());
            System.out.print(entry.getValue());
        });
    }
}
4MyBatis framework5Java中文社群1Java2JDK3Spring Framework

Performance Test

The JMH (Java Microbenchmark Harness) framework is used to benchmark the seven traversal methods. The required dependency is added to pom.xml and the benchmark class defines methods annotated with @Benchmark for each traversal.

... (benchmark code omitted for brevity) ...

The benchmark results (shown in the image below) indicate that, except for the parallelStream version, all other methods have almost identical average execution times.

Score represents the average execution time; the ± symbol shows the error margin. The parallelStream version shows a much higher throughput, while the other methods differ negligibly in performance.

Note: Results are based on JDK 1.8 / Mac mini (2018) / IDEA 2020.1

Performance Principle Analysis

By decompiling the compiled bytecode with javac and viewing it in IDEA, we see that Iterator‑based and for‑each loops over EntrySet generate identical bytecode, as do Iterator‑based and for‑each loops over KeySet. The only noticeable difference is that KeySet creates a temporary Integer variable during iteration.

Therefore, the performance gap between EntrySet and KeySet is minimal, contrary to some online claims.

Safety Test

Four traversal categories—Iterator, for‑each, Lambda, and Stream—are tested for safe removal of elements during iteration.

1. Iterator

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

Result: removal via iterator is safe.

2. For‑Each Loop

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

Result: removal inside a for‑each loop is unsafe.

3. Lambda

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

Result: removal inside a Lambda is unsafe. The correct approach is to use map.keySet().removeIf(key -> key == 1) before iteration.

4. Stream

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

Result: removal inside a Stream forEach is unsafe. The safe way is to filter out unwanted entries first:

map.entrySet().stream()
    .filter(e -> e.getKey() != 1)
    .forEach(e -> System.out.println("show:" + e.getKey()));

Summary

All four traversal categories were evaluated for performance and safety. Apart from the parallel Stream version, performance differences are negligible, while Lambda and Stream offer the most concise syntax. For safe removal during iteration, use iterator.remove(), removeIf, or filter before looping.

Choosing a traversal method should consider performance, safety, JDK version, readability, and elegance.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaperformanceHashMapStreamsiteration
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.