Java Notes
Complete guide to Java HashMap — internal working with hashing, buckets, linked lists, tree bins (Java 8+), load factor, rehashing, collision handling, and performance analysis.
What is HashMap?
java.util.HashMap is the most commonly used implementation of the Map interface. It stores key-value pairs using a hash table mechanism, providing O(1) average-case performance for get and put operations.
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, SerializableKey characteristics:
- No ordering guarantee (unlike LinkedHashMap or TreeMap)
- Allows one null key and multiple null values
- Not synchronized (not thread-safe)
- O(1) average for get/put/remove/containsKey
How put() Works — Step by Step
map.put("name", "Alice");Step 1: Compute Hash
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}This spreads the hash bits by XORing the upper 16 bits with the lower 16 bits. This helps distribute entries across buckets, especially when the table size is small.
Step 2: Find Bucket Index
int index = (table.length - 1) & hash; // Equivalent to hash % table.length
// Works because table.length is always power of 2Step 3: Handle the Bucket
Case A: Bucket is empty → Create new node, place it there.
Case B: Bucket has nodes → Walk the linked list:
- If key already exists (same hash AND equals() returns true) → replace value
- If key is new → append to end of chain (or tree)
Case C: Chain is too long (≥ TREEIFY_THRESHOLD = 8) → Convert linked list to Red-Black tree
Step 4: Check Load Factor
If size > threshold (capacity × loadFactor), resize the table (double capacity + rehash all entries).
Simplified put() Logic
How get() Works
Time: O(1) average (if good hash distribution), O(n) worst case with many collisions, O(log n) worst case with treeified bins.
Java 8 Treeification — LinkedList to Red-Black Tree
The Problem
When many keys hash to the same bucket, the linked list grows long, degrading get/put to O(n).
The Solution (Java 8+)
When a bucket's chain reaches 8 nodes (TREEIFY_THRESHOLD), and the table has at least 64 buckets (MIN_TREEIFY_CAPACITY), the chain is converted to a Red-Black tree:
static final int TREEIFY_THRESHOLD = 8; // Chain → Tree
static final int UNTREEIFY_THRESHOLD = 6; // Tree → Chain (on resize)
static final int MIN_TREEIFY_CAPACITY = 64;// TreeNode extends Node with tree pointers
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
boolean red;
}Performance Impact
| Scenario | Linked List | Red-Black Tree |
|---|---|---|
| ---------- | :-: | :-: |
| Search | O(n) | O(log n) |
| Insert | O(n) | O(log n) |
| Delete | O(n) | O(log n) |
This means even with a deliberately bad hash function, HashMap performance won't degrade below O(log n) per operation.
Load Factor and Resizing
Load Factor
static final float DEFAULT_LOAD_FACTOR = 0.75f;- Load factor = size / capacity
- When load factor exceeds threshold (default 0.75), the table resizes (doubles)
- Higher load factor = less memory, more collisions
- Lower load factor = more memory, fewer collisions
Resize (Rehashing) Process
// When size > capacity * 0.75:
// 1. Create new array of DOUBLE the capacity
// 2. Recalculate index for EVERY existing entry
// 3. Move entries to new positionsResize history (starting from default):
Tip: If you know the expected size, set initial capacity to avoid resizing:
// For 100 entries with load factor 0.75:
// Need capacity > 100/0.75 = 134 → next power of 2 = 256
Map<String, Integer> map = new HashMap<>(256);
// Or simpler: capacity = expectedSize / 0.75 + 1
Map<String, Integer> map = new HashMap<>(134); // Rounds up to 256 internallyComplete Example
import java.util.*;
public class HashMapDemo {
public static void main(String[] args) {
Map<String, Integer> ages = new HashMap<>();
// put — add entries
ages.put("Alice", 30);
ages.put("Bob", 25);
ages.put("Charlie", 35);
ages.put(null, 0); // null key allowed
ages.put("Diana", null); // null value allowed
// get — retrieve value
System.out.println("Alice: " + ages.get("Alice")); // 30
System.out.println("Unknown: " + ages.get("Eve")); // null
// getOrDefault — safer retrieval
int eveAge = ages.getOrDefault("Eve", -1); // -1
// containsKey / containsValue
System.out.println("Has Bob? " + ages.containsKey("Bob")); // true
System.out.println("Has age 25? " + ages.containsValue(25)); // true
// putIfAbsent — only adds if key missing
ages.putIfAbsent("Alice", 99); // NOT added — Alice already exists
ages.putIfAbsent("Eve", 28); // Added — Eve was missing
// replace
ages.replace("Bob", 26); // Replace value
ages.replace("Bob", 26, 27); // Replace only if current value matches
// compute / merge
ages.compute("Alice", (key, val) -> val + 1); // Alice: 31
ages.merge("Bob", 1, Integer::sum); // Bob: 28
// Remove
ages.remove("Charlie");
ages.remove("Diana", null); // Only removes if value matches
// Iteration
System.out.println("\n--- All entries ---");
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
// Java 8 forEach
ages.forEach((name, age) -> System.out.println(name + " → " + age));
}
}Map: {Alice=85, Bob=92, Charlie=78}
Alice's score: 85
Contains Bob: true
Keys: [Alice, Bob, Charlie]
Values: [85, 92, 78]
Entries: [Alice=85, Bob=92, Charlie=78]
After remove Charlie: {Alice=85, Bob=92}
Size: 2Time Complexity
| Operation | Average | Worst Case (many collisions) |
|---|---|---|
| ----------- | :-: | :-: |
put(key, value) | O(1) | O(log n)* |
get(key) | O(1) | O(log n)* |
remove(key) | O(1) | O(log n)* |
containsKey(key) | O(1) | O(log n)* |
containsValue(value) | O(n) | O(n) |
size() | O(1) | O(1) |
| Iteration | O(capacity + n) | O(capacity + n) |
| Resize | O(n) | O(n) |
*With treeification (Java 8+). Without it, worst case is O(n).
Important: equals() and hashCode() Contract
For HashMap to work correctly, keys MUST properly implement equals() and hashCode():
The Contract:
- If
a.equals(b)is true, thena.hashCode() == b.hashCode()must be true - If
a.hashCode() != b.hashCode(), thena.equals(b)must be false - Two unequal objects CAN have the same hashCode (collision — but should be rare)
What Happens Without Proper hashCode()
Map<Employee, String> map = new HashMap<>();
Employee emp = new Employee(1, "Alice");
map.put(emp, "Engineering");
// Without hashCode() override:
Employee lookup = new Employee(1, "Alice");
map.get(lookup); // Returns NULL! Different hash → different bucket → not found!Thread Safety
HashMap is NOT thread-safe. Concurrent modification can cause:
- Infinite loops (in older Java versions during resize)
- Lost updates
- ConcurrentModificationException during iteration
- Corrupted internal state
Solutions
// Option 1: ConcurrentHashMap (RECOMMENDED for multi-threaded)
Map<String, Integer> map = new ConcurrentHashMap<>();
// Option 2: Collections.synchronizedMap (wrapper)
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// Option 3: Hashtable (legacy — don't use)
Map<String, Integer> ht = new Hashtable<>();Java 8+ Map Methods
Map<String, Integer> scores = new HashMap<>();
// computeIfAbsent — compute value only if key is absent
scores.computeIfAbsent("Alice", k -> calculateScore(k));
// computeIfPresent — compute only if key exists
scores.computeIfPresent("Alice", (k, v) -> v + 10);
// compute — always compute
scores.compute("Alice", (k, v) -> (v == null) ? 0 : v + 1);
// merge — combine old and new values
scores.merge("Alice", 5, Integer::sum); // Add 5 to existing value
// replaceAll — transform all values
scores.replaceAll((key, value) -> value * 2);
// forEach
scores.forEach((k, v) -> System.out.println(k + ": " + v));Common Mistakes
1. Mutable Keys
// TERRIBLE — mutating a key after putting it in the map
List<String> key = new ArrayList<>(Arrays.asList("a", "b"));
map.put(key, "value");
key.add("c"); // Changes hashCode! Entry is now LOST in the wrong bucket!
map.get(key); // Returns null!Rule: Map keys should be immutable (String, Integer, or custom immutable classes).
2. Not Setting Initial Capacity
// WRONG — causes multiple resizes for 10000 entries
Map<String, String> map = new HashMap<>(); // Capacity 16
// BETTER — pre-size
Map<String, String> map = new HashMap<>(16384); // No resizing needed3. Confusing null Return
map.put("key", null);
Integer val = map.get("key"); // Returns null — but key EXISTS!
Integer val2 = map.get("other"); // Returns null — key DOESN'T exist!
// Use containsKey() to distinguish:
map.containsKey("key"); // true
map.containsKey("other"); // falseInterview Questions
Q1: How does HashMap work internally?
A: HashMap uses an array of buckets. The key's hashCode determines the bucket index. Collisions are handled by linked lists (or Red-Black trees when chain length ≥ 8 in Java 8+). Get/put are O(1) on average.
Q2: What happens when two keys have the same hashCode?
A: They go to the same bucket (collision). HashMap stores both in a linked list at that bucket. On retrieval, it walks the chain and uses equals() to find the right entry.
Q3: Why is the default load factor 0.75?
A: It's a trade-off between time and space. Lower values waste memory, higher values cause more collisions. 0.75 provides good average performance with reasonable memory usage.
Q4: What is treeification in Java 8 HashMap?
A: When a bucket's collision chain grows to 8 nodes (and table has ≥ 64 buckets), the linked list is converted to a Red-Black tree, improving worst-case from O(n) to O(log n).
Q5: Why must table size be a power of 2?
A: Because (table.length - 1) & hash replaces the expensive modulo operation (hash % length) with a fast bitwise AND. This only works when length is a power of 2.
Q6: Can HashMap have a null key?
A: Yes, exactly one. Null key always goes to bucket index 0.
Q7: What happens if you don't override hashCode() but override equals()?
A: Two logically equal objects may have different hashCodes, placing them in different buckets. You won't be able to find entries using a new but "equal" key object.
Q8: What happens when two keys have the same hashCode in HashMap?
A: A hash collision occurs. Both entries are stored in the same bucket. In Java 7, they form a linked list at that bucket. In Java 8+, if the chain length exceeds 8 (with capacity ≥ 64), the linked list is converted to a Red-Black tree for O(log n) lookup instead of O(n).
Q9: Why is the initial capacity recommended to be a power of 2?
A: HashMap uses hash & (capacity - 1) instead of hash % capacity for bucket index calculation. This bitwise AND only works correctly to distribute evenly when capacity is a power of 2. If you specify a non-power-of-2 capacity, HashMap rounds it up to the next power of 2.
Q10: What is the significance of load factor 0.75?
A: Load factor 0.75 is a trade-off between time and space. Lower values (e.g., 0.5) reduce collisions but waste memory. Higher values (e.g., 1.0) save memory but increase collision probability and degrade performance. At 0.75, the average chain length stays below 2, giving near-O(1) performance.
Q11: Can we use a mutable object as a HashMap key?
A: Technically yes, but it's dangerous. If the object's hashCode changes after being used as a key (due to mutation), the entry becomes unretrievable — it's stored in a bucket based on the old hash but searched using the new hash. Always use immutable objects (String, Integer, custom immutable classes) as keys.
Q12: How does HashMap differ from ConcurrentHashMap?
A: HashMap is not thread-safe. ConcurrentHashMap (Java 8+) uses node-level locking with CAS operations — multiple threads can read/write different segments simultaneously. ConcurrentHashMap doesn't allow null keys or values, never throws ConcurrentModificationException, and provides atomic operations like computeIfAbsent().
Summary
| Aspect | HashMap |
|---|---|
| Structure | Array of buckets (linked lists + trees) |
| Average time | O(1) for get/put/remove |
| Worst case | O(log n) with treeification |
| Ordering | None |
| Null keys | One allowed |
| Thread-safe | No (use ConcurrentHashMap) |
| Load factor | 0.75 default |
| Initial capacity | 16 (always power of 2) |
| Growth | Doubles on resize |
| Key requirement | Immutable, proper equals/hashCode |
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for HashMap in Java.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, collections, framework, map
Related Java Master Course Topics