Java Collections – HashMap

Create a HashMap by parsing CSV. Declare and use a multi-map (a HashMap inside a HashMap).

“Don’t judge each day by the harvest you reap but by the seeds that you plant.”
― Robert Louis Stevenson

Check out the previous parts of this Java Collections Guide: Introduction, Part 1 – Collection, Part 2 – Sets and Part 3 – ArrayList.

1. Introduction

The HashMap implements the Map interface and provides the abstraction of a dictionary mapping of keys to values. It is a part of the Java Collections Framework but does not fall under the Iterable and Collection hierarchy. In this article, we learn how to use HashMaps.

In a previous guide, we covered some aspects of a HashMap usage including creation, iteration and search. In this article, we present some more HashMap use cases including parsing a CSV into a HashMap as well as a multi-map.

Java Map Hierarchy
Java Map Hierarchy

2. Adding Entries to HashMap

Quite simple. Use put().

Map<String,Integer> namefreq = new HashMap<>();
namefreq.put("Petra", 14);
namefreq.put("Mario", 11);
namefreq.put("Kasandra", 23);
namefreq.forEach((k, v) -> System.out.printf("%s => %s%n", k, v));

// prints:
Kasandra => 23
Petra => 14
Mario => 11

Note that there is no ordering of items on iteration since we are using a HashMap. If you need predictable iteration order, use a LinkedHashMap.

3. Parse CSV to a HashMap

And here is a slightly more complex example of a Map in use. We parse a CSV and create a Map of names to counts. (For the curious, the data comes from US Census data; it is a table of name, state and frequency count). The sample data looks like this:

Id,Name,Year,Gender,State,Count
1,Mary,1910,F,AK,14
2,Annie,1910,F,AK,12
3,Anna,1910,F,AK,10
4,Margaret,1910,F,AK,8
5,Helen,1910,F,AK,7
...

The code reads lines in a stream, skips the header line, splits the line into fields, filters the row for a state (“CA“) and female names (“F“). Finally the data is collected into a HashMap of name to frequency count.

Pattern pattern = Pattern.compile(",");
String csvFile = "StateNames.csv";
try (BufferedReader in = new BufferedReader(new FileReader(csvFile));){
    Map<String,Integer> namefreq = in
	.lines()
	.skip(1)
	.map(x -> pattern.split(x))
	.filter(x -> x[4].equals("CA") && x[3].equals("F"))
	.collect(HashMap::new, (map, x) ->
		 map.put(x[1], Integer.parseInt(x[5])),
		 Map::putAll);
    namefreq.forEach((k, v) -> System.out.println(k + " => " + v));
}

// prints:
Kasandra => 23
Petra => 14
Mario => 11
Nichelle => 6
Debbi => 46
Karly => 7
...

4. Store a Map Inside a Map (Multi-Map)

A multi-map is a map of a key to a value which is itself a map. It is useful for storing multiple levels of data. We use this in the following example to store a map of (year => count) for each name. So the mapping follows a structure of (name => (year => count)).

The sample data is the same CSV file shown in the above example.

Pattern pattern = Pattern.compile(",");
String csvFile = "StateNames.csv";
try (BufferedReader in = new BufferedReader(new FileReader(csvFile));){
    Map<String,Map<Integer,Integer>> namefreq = in
	.lines()
	.skip(1)
	.map(x -> pattern.split(x))
	.filter(x -> x[4].equals("CA"))
	.collect(HashMap::new, (map, x) ->
		 map.compute(x[1], (k, v) -> {
			 if ( v == null )
			     v = new HashMap<Integer,Integer>();
			 v.put(Integer.parseInt(x[2]),
			       Integer.parseInt(x[5]));
			 return v;
		     }),
		 Map::putAll);
    namefreq.forEach((k, v) -> {
	    System.out.println(k + ":");
	    v.forEach((kk, vv) -> {
		    System.out.println("  " + kk + " => " + vv);
		});
	});
}

As before, the code streams lines from a CSV file, splits the line into fields, applies a filter to select the state (“CA“) and collects the values in a multi-map. The multi-map is updated using the Map’s compute() method which applies a function to the specified mapping as follows

map.compute(key, <function to compute value>);

The function (which is specified as a lambda) is invoked with the currently existing (key, value) and is expected to update the value to the required value. Here we create a new HashMap of (year => count) and add the mapping. The returned HashMap is then stored as the value for the key.

map.compute(key, (key, value) -> {
    if ( value == null ) value = new HashMap<Integer,Integer>();
    value.put(<year>, <count>);
    return value;
});

4.1. Iterating over the Multi-Map

How can we loop through the outer map as well as the map contained within this map? Quite simple. Use a forEach() inside a forEach(). In the output below, we can see that the name “Leigh” has multiple mappings inside the map.

namefreq.forEach((k, v) -> {
	System.out.println(k + ":");
	v.forEach((kk, vv) -> {
		System.out.println("  " + kk + " => " + vv);
	    });
    });

// prints:
Nichelle:
 2007 => 6
Bilal:
 1996 => 14
Bowen:
 1970 => 6
...
Mario:
 1987 => 11
Leigh:
 1941 => 5
 1974 => 51
 1999 => 6
Santa:
 1979 => 11

Summary

We examined some usage scenarios of the HashMap including parsing CSV and storing a multi-map.

See Also

Leave a Reply

Your email address will not be published. Required fields are marked *