How HashMap Handles Collisions
Collisions in a HashMap
occur when multiple keys hash to the same bucket index. This situation arises because different keys can produce the same hash value. To handle such cases, HashMap
employs separate chaining, where it stores multiple key-value pairs in a bucket using a linked list (or a balanced tree for performance optimization, starting from Java 8).
Let’s dive into an example to understand how this works.
The Code: Simulating Collisions
The following example demonstrates what happens when multiple keys in a HashMap
collide due to having the same hash code:
import java.util.HashMap;
public class HashMapCollisionExample {
public static void main(String[] args) {
var map = new HashMap<KeyWithSameHash, String>();
map.put(new KeyWithSameHash("Key1"), "Value1");
map.put(new KeyWithSameHash("Key2"), "Value2");
map.put(new KeyWithSameHash("Key3"), "Value3");
// prints Value3
System.out.println(map.get(new KeyWithSameHash("Key3")));
}
}
class KeyWithSameHash {
private String key;
public KeyWithSameHash(String key) {
this.key = key;
}
@Override
public int hashCode() {
return 1001; // Causes all keys to hash to the same bucket
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
KeyWithSameHash other = (KeyWithSameHash) obj;
return key.equals(other.key);
}
@Override
public String toString() {
return key;
}
}
Step-by-Step Breakdown
-
Hash Code Calculation
When you callmap.put(new KeyWithSameHash("Key3"), "Value3")
, thehashCode()
method of the key returns1001
for all instances, forcing all keys to map to the same bucket. -
Bucket Index Determination
Although the hash code is1001
, the bucket index is calculated using a bitwise operation (hashCode % bucketArrayLength
), ensuring entries are distributed across available buckets. However, in this case, all keys land in the same bucket because their hash codes are identical. -
Collision Handling
Since multiple keys are mapped to the same bucket,HashMap
handles the collision using a linked list (or balanced tree). Each entry in the bucket is stored as a node in the list. -
Key Lookup
When you retrieve a value using a key (e.g.,new KeyWithSameHash("Key3")
), theHashMap
:- Finds the bucket for the key.
- Traverses the linked list in that bucket.
- Compares each key in the list using the
equals()
method until it finds a match.
-
Value Retrieval
Once the key is located (whereequals()
returnstrue
), the corresponding value is returned.
Retrieval Complexity
Despite all keys having the same hash code, the HashMap
successfully retrieves the correct value due to its equals()
comparison mechanism. However, retrieval complexity can degrade to (O(n)) if all keys hash to the same bucket and the bucket uses a linked list, or to (O(log n)) if the bucket uses a balanced tree (introduced in Java 8).
Wrap Up
- Efficient Collision Handling:
HashMap
can handle collisions gracefully using separate chaining, ensuring reliable behavior even when hash codes collide. - Importance of Proper
hashCode
Implementation: A poor hash code implementation, as shown in this example, can lead to performance degradation since all keys fall into the same bucket. - Balanced Trees for Performance: Starting with Java 8,
HashMap
replaces the linked list with a balanced tree when collisions exceed a threshold, improving lookup times in heavily collided buckets.
Happy coding! 💻