HashMap
HashMap is Java’s most popular key-value store. It maps unique keys to values, lets you look up any value in near-instant time, and handles a huge range of problems — from counting word frequencies to building simple in-memory caches.
What is HashMap?
HashMap<K, V> lives in java.util and implements the Map interface. Every entry is a key → value pair. Keys must be unique; values can repeat. It allows one null key and any number of null values.
import java.util.HashMap;
public class BasicMap {
public static void main(String[] args) {
HashMap<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Carol", 92);
System.out.println(scores.get("Bob")); // 87
System.out.println(scores);
}
}
Output:
87
{Alice=95, Bob=87, Carol=92}
Note:
HashMapdoes not guarantee insertion order. If you need predictable order, useLinkedHashMap(insertion order) orTreeMap(sorted order).
Creating a HashMap
import java.util.HashMap;
import java.util.Map;
public class CreateMap {
public static void main(String[] args) {
// 1. Default constructor (initial capacity 16, load factor 0.75)
HashMap<String, Integer> map1 = new HashMap<>();
// 2. Custom initial capacity
HashMap<String, Integer> map2 = new HashMap<>(32);
// 3. Custom capacity + load factor
HashMap<String, Integer> map3 = new HashMap<>(32, 0.5f);
// 4. Copy from another map (Map.of is immutable; copy to make mutable)
Map<String, Integer> source = Map.of("x", 1, "y", 2);
HashMap<String, Integer> map4 = new HashMap<>(source);
System.out.println(map4);
}
}
Output:
{x=1, y=2}
Core Operations
put, get, and containsKey
import java.util.HashMap;
public class CoreOps {
public static void main(String[] args) {
HashMap<String, String> capitals = new HashMap<>();
// Add / update entries
capitals.put("France", "Paris");
capitals.put("Japan", "Tokyo");
capitals.put("India", "New Delhi");
// Read
System.out.println(capitals.get("Japan")); // Tokyo
System.out.println(capitals.get("Germany")); // null (missing key)
// Check existence
System.out.println(capitals.containsKey("France")); // true
System.out.println(capitals.containsValue("Rome")); // false
// Overwrite
capitals.put("India", "Mumbai"); // wrong, but shows overwrite
System.out.println(capitals.get("India")); // Mumbai
}
}
Output:
Tokyo
null
true
false
Mumbai
remove and size
import java.util.HashMap;
public class RemoveOps {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "One");
map.put(2, "Two");
map.put(3, "Three");
map.remove(2); // remove by key
map.remove(3, "Wrong value"); // conditional remove — does nothing here
System.out.println(map.size()); // 2
System.out.println(map); // {1=One, 3=Three}
}
}
Output:
2
{1=One, 3=Three}
Iterating a HashMap
There are four common ways to walk through a HashMap.
import java.util.HashMap;
import java.util.Map;
public class IterateMap {
public static void main(String[] args) {
HashMap<String, Integer> pop = new HashMap<>();
pop.put("Tokyo", 14000000);
pop.put("Delhi", 32941000);
pop.put("Shanghai", 28516904);
// 1. entrySet() — most efficient, gives both key & value
for (Map.Entry<String, Integer> entry : pop.entrySet()) {
System.out.println(entry.getKey() + " → " + entry.getValue());
}
// 2. keySet()
for (String city : pop.keySet()) {
System.out.println(city);
}
// 3. values()
for (int population : pop.values()) {
System.out.println(population);
}
// 4. forEach (lambda, Java 8+)
pop.forEach((city, p) -> System.out.println(city + ": " + p));
}
}
Tip: Prefer
entrySet()when you need both key and value — it avoids the second hash lookup thatget(key)inside akeySet()loop would require.
Useful Modern Methods (Java 8+)
HashMap gained several powerful methods in Java 8 that eliminate common boilerplate.
import java.util.HashMap;
public class ModernMethods {
public static void main(String[] args) {
HashMap<String, Integer> wordCount = new HashMap<>();
// getOrDefault — safe fallback instead of null-check
wordCount.put("hello", 3);
System.out.println(wordCount.getOrDefault("hello", 0)); // 3
System.out.println(wordCount.getOrDefault("world", 0)); // 0
// putIfAbsent — only inserts if key is missing
wordCount.putIfAbsent("hello", 99); // ignored, key exists
wordCount.putIfAbsent("java", 1); // inserted
System.out.println(wordCount.get("hello")); // 3
System.out.println(wordCount.get("java")); // 1
// merge — great for counting / accumulating
String[] words = {"apple", "banana", "apple", "cherry", "banana", "apple"};
HashMap<String, Integer> freq = new HashMap<>();
for (String w : words) {
freq.merge(w, 1, Integer::sum);
}
System.out.println(freq); // {apple=3, banana=2, cherry=1}
// computeIfAbsent — create value lazily
HashMap<String, java.util.List<String>> groups = new HashMap<>();
groups.computeIfAbsent("fruits", k -> new java.util.ArrayList<>()).add("mango");
groups.computeIfAbsent("fruits", k -> new java.util.ArrayList<>()).add("kiwi");
System.out.println(groups); // {fruits=[mango, kiwi]}
}
}
Output:
3
0
3
1
{apple=3, banana=2, cherry=1}
{fruits=[mango, kiwi]}
Null Keys and Values
import java.util.HashMap;
public class NullKeys {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put(null, "null key is allowed");
map.put("key1", null); // null value is allowed
map.put("key2", null); // multiple null values fine
System.out.println(map.get(null)); // null key is allowed
System.out.println(map.get("key1")); // null
}
}
Warning: Having a
nullkey meansmap.get(null)returningnullis ambiguous — is the key missing, or is the value actuallynull? UsecontainsKey(null)to distinguish the two cases.
HashMap vs Hashtable vs LinkedHashMap vs TreeMap
| Feature | HashMap | Hashtable | LinkedHashMap | TreeMap |
|---|---|---|---|---|
| Thread-safe | No | Yes (synchronized) | No | No |
| Null keys | 1 allowed | Not allowed | 1 allowed | Not allowed |
| Order | None | None | Insertion order | Sorted (natural/comparator) |
| Performance | O(1) avg | O(1) avg (+ lock) | O(1) avg | O(log n) |
| Since | Java 1.2 | Java 1.0 | Java 1.4 | Java 1.2 |
For thread-safe use cases, prefer ConcurrentHashMap from java.util.concurrent over the legacy Hashtable.
Under the Hood
Understanding the internals helps you write faster code and avoid surprises.
Hash Table Structure
A HashMap internally holds an array of buckets (called Node[] table). When you call put(key, value):
- Java calls
key.hashCode()and applies an additional spread function ((h = key.hashCode()) ^ (h >>> 16)) to reduce collisions from poor hash functions. - The result is bit-masked to an array index:
index = hash & (capacity - 1). - The entry is stored in the bucket at that index.
If two keys hash to the same bucket (collision), they form a linked list at that slot (chaining). Since Java 8, once a bucket’s list grows beyond 8 entries, it converts to a Red-Black Tree, keeping worst-case lookup at O(log n) rather than O(n).
Capacity and Load Factor
The default initial capacity is 16 and the default load factor is 0.75. When the number of entries exceeds capacity × loadFactor (i.e., 12 entries in a fresh map), the table rehashes — it doubles in capacity and redistributes every entry. Rehashing is expensive (O(n)), so if you know you’ll store many items, pass an estimated capacity to the constructor to avoid multiple resizes:
// Storing ~100 entries: pre-size to avoid rehash at 12, 24, 48, 96
HashMap<String, Integer> map = new HashMap<>(128); // next power-of-two above 100/0.75
hashCode and equals Contract
HashMap relies on two methods from Object:
hashCode()— determines the bucketequals()— resolves identity within a bucket
If you use a custom object as a key, you must override both consistently:
import java.util.HashMap;
import java.util.Objects;
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public int hashCode() { return Objects.hash(x, y); }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
}
public class CustomKeyMap {
public static void main(String[] args) {
HashMap<Point, String> map = new HashMap<>();
map.put(new Point(1, 2), "origin-ish");
// Works because equals+hashCode are consistent
System.out.println(map.get(new Point(1, 2))); // origin-ish
}
}
Output:
origin-ish
Warning: If you override
equals()but nothashCode(), two “equal” objects will land in different buckets andget()will returnnulleven though the key logically exists. This is one of the most common bugs with custom keys. See Object Class for the full contract.
Memory Layout
Each entry is a Node<K,V> object containing: hash (int), key, value, and next (pointer to next node in the same bucket). A map with many collisions wastes memory via these linked nodes. Choosing good key types (like String, Integer) with well-distributed hash codes keeps the map lean and fast.
Common Patterns
Frequency Counter
import java.util.HashMap;
public class FrequencyCounter {
public static void main(String[] args) {
int[] nums = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
HashMap<Integer, Integer> freq = new HashMap<>();
for (int n : nums) {
freq.merge(n, 1, Integer::sum);
}
System.out.println(freq);
}
}
Output:
{1=2, 2=1, 3=2, 4=1, 5=3, 6=1, 9=1}
Grouping (one-to-many)
import java.util.*;
public class Grouping {
public static void main(String[] args) {
String[] words = {"ant", "bat", "arc", "bear", "bell", "cap"};
HashMap<Character, List<String>> byFirst = new HashMap<>();
for (String w : words) {
byFirst.computeIfAbsent(w.charAt(0), k -> new ArrayList<>()).add(w);
}
byFirst.forEach((ch, list) -> System.out.println(ch + " → " + list));
}
}
Output:
a → [ant, arc]
b → [bat, bear, bell]
c → [cap]
Related Topics
- Working of HashMap (Internals) — deep dive into the bucket array, treeification, and resize mechanics
- LinkedHashMap — HashMap that remembers insertion order
- TreeMap — Map that keeps keys sorted by natural order or a custom Comparator
- HashMap vs Hashtable — when to use which and why Hashtable is mostly legacy
- Map Interface — the full Map API and all its implementations at a glance
- Concurrent Collections — thread-safe alternatives like ConcurrentHashMap