The HashMap Class
Objective
Understand HashMap, the default Map implementation: key/value pairs are stored in a hash table, giving average constant-time get/put/remove at the cost of any guaranteed iteration order.
Use Cases
- Fast key-based lookup — configuration, caches, or lookup tables where order genuinely doesn't matter.
- Counting or aggregating values by key (word frequency, grouping) without needing the keys back in any particular order.
- Pre-sizing the table via the capacity/fill-ratio constructor when the eventual entry count is roughly known, to avoid rehashing during a large batch of
putcalls.
Deep Dive
HashMap extends AbstractMap, implements Map
javaclass HashMap<K, V>Four constructors:
javaHashMap<String, Double> a = new HashMap<>(); // default capacity 16, load factor 0.75
HashMap<String, Double> b = new HashMap<>(existingMap); // initialized from another Map
HashMap<String, Double> c = new HashMap<>(64); // initial capacity 64
HashMap<String, Double> d = new HashMap<>(64, 0.5f); // capacity 64, load factor 0.5Capacity and load factor mean exactly what they mean for HashSet — in fact HashSet is implemented internally as a thin wrapper around a HashMap, so the two share the same table, collision, and treeification behavior.
Reading entries via a set-view
javaHashMap<String, Double> hm = new HashMap<>();
hm.put("John Doe", 3434.34);
hm.put("Tom Smith", 123.22);
hm.put("Jane Baker", 1378.00);
hm.put("Tod Hall", 99.22);
hm.put("Ralph Smith", -19.08);
Set<Map.Entry<String, Double>> set = hm.entrySet();
for (Map.Entry<String, Double> me : set) {
System.out.print(me.getKey() + ": ");
System.out.println(me.getValue());
}
// order is table-layout dependent, not insertion order — e.g.:
// Ralph Smith: -19.08
// Tom Smith: 123.22
// John Doe: 3434.34
// Tod Hall: 99.22
// Jane Baker: 1378.0entrySet() returns a live view backed by the map itself, not a copy; Map.Entry's getKey()/getValue() read each pair.
put() replaces the value of an existing key
javadouble balance = hm.get("John Doe");
hm.put("John Doe", balance + 1000); // same key -> old value overwritten, map still has one "John Doe" entry
System.out.println("John Doe's new balance: " + hm.get("John Doe")); // 4434.34put(K key, V value) returns the previous value associated with key, or null if the key was new — useful for detecting whether a put was actually an update.
Watch it happen: put() spreading keys across buckets
Each put(key, value) computes key.hashCode(), spreads its bits, and masks it against capacity - 1 to pick a bucket. Two keys landing in the same bucket don't overwrite each other — they chain, and equals() is what tells them apart on a later get():
Trade-offs
No iteration-order guarantee, and it can change between runs or after a resize — if a stable order matters, use
LinkedHashMap(insertion order); if a sorted order matters, useTreeMap.Mutating a field involved in a key's
hashCode()after it's already in the map breaks lookups silently — the entry sits in the bucket its old hash code pointed to, soget()/containsKey()with an equal-looking key can returnnull/falseinstead of finding it:javaclass Point { int x; /* hashCode() based on x */ } Point p = new Point(1); HashMap<Point, String> map = new HashMap<>(); map.put(p, "origin"); p.x = 2; // mutated after insertion map.get(p); // may return null — p is now in the wrong bucketAverage O(1) operations assume a reasonably distributed
hashCode()— a poor hash function that collides heavily degradesget/put/removetoward O(n) within a bucket (O(log n) since JDK 8, once a bucket treeifies past 8 entries), since every colliding key has to be checked withequals().HashMapis not synchronized and allows onenullkey — for concurrent access, useConcurrentHashMapinstead (see the HTTP Sessions Under the Hood concept for it in a real server-side use).