
Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:
Once the early-adopter seats are all used, the price will go up and stay at $33/year.
A Map in Java is a data structure that stores key-value pairs, allowing for efficient lookups, updates, and deletions by key. Understanding how to iterate through a Map is crucial for some programming tasks.
In this tutorial, we’ll look at the different ways of iterating through the entries of a Map in Java and write a benchmark test to determine the most efficient method.
Simply put, we can extract the contents of a Map using entrySet(), keySet(), or values(). Since these are all sets, similar iteration principles apply to all of them.
Before we dive into iteration techniques, let’s review the three primary methods for accessing Map contents:
First, let’s see how to iterate through a Map using the entrySet() method:
long iterateUsingEnhancedForLoopAndEntrySet(Map<Integer, Integer> map) {
long sum = 0;
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
sum += entry.getValue();
}
return sum;
}
Here, we extract the Set of entries from our Map, and then we iterate through them using the classical for-each approach and sum the values.
Alternatively, if we need the keys initially but may need to access the values, we can use the keySet() method:
long iterateUsingKeySetAndEnhanceForLoop(Map<Integer, Integer> map) {
long sum = 0;
for (Integer key : map.keySet()) {
sum += map.get(key);
}
return sum;
}
In the code above, we use the keys to retrieve the corresponding values in the Map and sum them together.
Sometimes, we’re only interested in the values in a map, no matter which keys are associated with them. In such case, values() is our best choice:
long iterateValuesUsingValuesMethodAndEnhanceForLoop(Map<Integer, Integer> map) {
long sum = 0;
for (Integer value : map.values()) {
sum += value;
}
return sum;
}
Here, we invoke the values() method on a Map object and sum all the values together without needing to interact with the keys.
Another approach to perform the iteration is using an Iterator. Let’s iterate over the map using an Iterator and entrySet():
long iterateUsingIteratorAndEntrySet(Map<Integer, Integer> map) {
long sum = 0;
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, Integer> pair = iterator.next();
sum += pair.getValue();
}
return sum;
}
In the code above, we use Iterator and entrySet() to traverse the Map entries, summing the values.
Similarly, we can iterate through the Map using an Iterator and keySet():
long iterateUsingIteratorAndKeySet(Map<Integer, Integer> map) {
long sum = 0;
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
sum += map.get(key);
}
return sum;
}
Like the entrySet() method, we get an Iterator instance by invoking the iterator() method on the keySet(). Then, we iterate through Map and get its values through the keys.
We can also walk through the map’s values using an Iterator and the values() method:
long iterateUsingIteratorAndValues(Map<Integer, Integer> map) {
long sum = 0;
Iterator iterator = map.values().iterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
sum += value;
}
return sum;
}
Here, we are only interested in the values in the Map collection. After defining an iterator object from the Map, we iterate through the values and find their sum.
Since version 8, Java supports the forEach() method and lambda expressions, which can be used with maps. The forEach() method is best suited for performing isolated actions on each map entry:
void iterateUsingLambdaAndForEach(Map<Integer, Integer> map) {
map.forEach((k, v) -> LOGGER.info("Key: {}, Value: {}", k, v));
}
In the code above, we log each key-value pair to the console, which is an isolated action that doesn’t depend on other entries.
While the forEach() method is convenient for isolated actions, it’s generally not recommended for aggregations like summing values across all entries:
long iterateValuesUsingLambdaAndForEach(Map<Integer, Integer> map) {
AtomicLong sum = new AtomicLong(0);
map.values().forEach(v -> sum.addAndGet(v));
return sum.get();
}
Here, we extract only the values and add them together. For aggregations, the Stream API provides more appropriate and efficient methods.
Stream API is one significant feature of Java 8. We can use it to iterate through a Map as well.
Stream API should be used when we’re planning on doing some additional Stream processing. Otherwise, it’s just a simple forEach() as described previously.
Let’s take entrySet() as the example to see how Stream API works:
long iterateUsingStreamAPIAndEntrySet(Map<Integer, Integer> map) {
return map.entrySet()
.stream()
.mapToLong(Map.Entry::getValue)
.sum();
}
Also, we can use the keySet() method with Stream API to iterate through a Map object:
long iterateUsingStreamAPIAndKeySet(Map<Integer, Integer> map) {
return map.keySet()
.stream()
.mapToLong(map::get)
.sum();
}
Finally, we can also use the parallelStream() method of the Stream API:
long iterateMapUsingParallelStreamApi(Map<Integer, Integer> map) throws IOException {
return map.entrySet()
.parallelStream()
.mapToLong(Map.Entry::getValue)
.sum();
}
This approach is best suited for parallel processing.
The Eclipse Collection is a high-performance collection with a smaller memory footprint than the standard Java collection. Let’s iterate through the MutableMap object and sum up the keys and values:
long iterateEclipseMap(MutableMap<Integer, Integer> mutableMap) throws IOException {
AtomicLong sum = new AtomicLong(0);
mutableMap.forEachKeyValue((key, value) -> {
sum.addAndGet(value);
});
return sum.get();
}
In the code above, we invoke forEachKeyValue() on a MutableMap object to iterate through the Map and use a lambda expression to get the keys and values. Then, we sum the values together.
Furthermore, let’s iterate over IterableMap from Apache Commons Collections:
long iterateUsingMapIteratorApacheCollection(IterableMap<Integer, Integer> map) {
long sum = 0;
MapIterator<Integer, Integer> iterate = map.mapIterator();
while (iterate.hasNext()) {
iterate.next();
sum += iterate.getValue();
}
return sum;
}
We create a MapIterator object, which is part of Apache Commons Collections, and pass the IterableMap object to it. We then invoke the mapIterator() method on the IterableMap object to obtain a MapIterator type.
Let’s compare the performance of the various options of iterating over a map using the Java Microbench Harness (JMH).
Let’s write our JMH benchmark setup:
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.MICROSECONDS)
@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.MICROSECONDS)
public class MapIterationBenchmark {
@Param({"100","1000","10000","100000"})
public int size;
MapIteration mapIteration = new MapIteration();
Map<Integer, Integer> map;
IterableMap<Integer, Integer> iterableMap;
MutableMap<Integer, Integer> mutableMap;
// ...
}
Then, let’s create a setup() method:
@Setup(Level.Trial)
public void setup() {
map = new HashMap<>();
iterableMap = new HashedMap<>();
mutableMap = UnifiedMap.newMap();
for (int i = 0; i < size; i++) {
map.put(i, i);
iterableMap.put(i, i);
mutableMap.put(i, i);
}
}
The setup() method tests each method with a map size of 100,000 elements.
Let’s look at the benchmark results for Map iteration methods for 100,000 elements:
Benchmark (size) Mode Cnt Score Error Units
iterateEclipseMap 100000 avgt 5 0.003 ± 0.003 s/op
iterateKeysUsingKeySetAndEnhanceForLoop 100000 avgt 5 0.003 ± 0.006 s/op
iterateMapUsingParallelStreamApi 100000 avgt 5 0.001 ± 0.001 s/op
iterateUsingEnhancedForLoopAndEntrySet 100000 avgt 5 0.003 ± 0.005 s/op
iterateUsingIteratorAndEntrySet 100000 avgt 5 0.002 ± 0.002 s/op
iterateUsingIteratorAndKeySet 100000 avgt 5 0.004 ± 0.003 s/op
iterateUsingIteratorAndValues 100000 avgt 5 0.003 ± 0.004 s/op
iterateUsingKeySetAndEnhanceForLoop 100000 avgt 5 0.003 ± 0.003 s/op
iterateUsingMapIteratorApacheCollection 100000 avgt 5 0.004 ± 0.010 s/op
iterateUsingStreamAPIAndEntrySet 100000 avgt 5 0.002 ± 0.001 s/op
iterateUsingStreamAPIAndKeySet 100000 avgt 5 0.003 ± 0.002 s/op
iterateValuesUsingLambdaAndForEach 100000 avgt 5 0.002 ± 0.001 s/op
Notably, these results are similar for other map sizes (100, 1000, 10000), with some variations in performance as the size increases.
However, we should interpret these results with caution. The error rates are relatively high compared to the scores, which suggests that the measurements aren’t very precise.
Despite the high error rates, we can still make some observations:
In this article, we focused on a critical but straightforward operation: iterating through the entries of a Map.
We explored a couple of methods that can only be used with Java 8+, namely Lambda expressions and the Stream API. Finally, we benchmarked different methods for iterating through a Map object.