Introduction
The HashMap class in Java is one of the most widely used implementations of the Map interface in the Java Collections Framework. It stores data in key-value pairs, where each key is unique and maps to a single value. HashMap provides constant-time performance for basic operations like get() and put() under ideal conditions, making it highly efficient for lookups, caching, and data indexing. Understanding the fundamentals of HashMap—including its internal structure, usage, performance characteristics, and best practices—is essential for effective data management in Java applications.
1. Key Features of HashMap
- Stores key-value pairs: Each key maps to exactly one value.
- Allows one
nullkey and multiplenullvalues. - Does not maintain insertion order (use
LinkedHashMapfor ordered iteration). - Not synchronized: Not thread-safe by default (use
ConcurrentHashMapfor concurrent access). - Implements
Map,Cloneable, andSerializable. - Backed by a hash table: Uses hashing to store and retrieve elements.
2. Creating and Initializing a HashMap
Basic Declaration
import java.util.HashMap; import java.util.Map; Map<String, Integer> phoneBook = new HashMap<>();
Best Practice: Program to the
Mapinterface, not the implementation.
Initialization with Values (Java 9+)
Map<String, Integer> scores = Map.of(
"Alice", 95,
"Bob", 87,
"Charlie", 92
); // Immutable map
// For mutable HashMap:
Map<String, Integer> mutableScores = new HashMap<>(Map.of("Alice", 95, "Bob", 87));
Pre-Java 9 Initialization
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 95);
map.put("Bob", 87);
3. Common Operations
A. Adding/Updating Entries
map.put("David", 88); // Adds new entry
map.put("Alice", 98); // Updates existing value
B. Retrieving Values
int aliceScore = map.get("Alice"); // 98
int unknown = map.get("Eve"); // null (key not present)
Note:
get()returnsnullif the key is not found or if the value isnull.
C. Checking Key/Value Existence
boolean hasAlice = map.containsKey("Alice"); // true
boolean hasScore99 = map.containsValue(99); // false
D. Removing Entries
map.remove("Bob"); // Removes key "Bob"
map.remove("Alice", 95); // Removes only if value is 95 (Java 8+)
E. Size and Emptiness
int size = map.size(); // Number of key-value pairs boolean isEmpty = map.isEmpty();
F. Clearing All Entries
map.clear(); // Removes all mappings
4. Iterating Over a HashMap
A. Iterating Over Keys
for (String key : map.keySet()) {
System.out.println(key + " -> " + map.get(key));
}
B. Iterating Over Values
for (Integer value : map.values()) {
System.out.println(value);
}
C. Iterating Over Entries (Most Efficient)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
D. Using forEach (Java 8+)
map.forEach((key, value) -> System.out.println(key + " -> " + value));
5. Internal Working: Hashing and Buckets
- When a key-value pair is added,
HashMap:
- Calls
hashCode()on the key to compute a hash code. - Applies a supplemental hash function to defend against poor hash codes.
- Uses the hash to determine the bucket index (array position).
- Stores the entry in a linked list (or tree if bucket size > 8) at that index.
Collision Handling:
- Java 8+ converts linked lists to balanced trees when a bucket has more than 8 entries (improves worst-case performance from O(n) to O(log n)).
6. Performance Characteristics
| Operation | Average Time Complexity | Worst Case |
|---|---|---|
get(key) | O(1) | O(log n) (Java 8+, after tree conversion) |
put(key, value) | O(1) | O(log n) |
remove(key) | O(1) | O(log n) |
| Iteration | O(n) | O(n) |
Note: Performance assumes a good hash function and proper initial capacity.
7. Important Considerations
A. Key Requirements
hashCode()andequals()must be consistent:- If two keys are equal (
equals()returnstrue), they must have the samehashCode(). - Override both methods together in custom key classes.
B. Initial Capacity and Load Factor
- Default initial capacity: 16
- Default load factor: 0.75
- When size > capacity × load factor, the map rehashes (resizes and reinserts all entries).
Optimization: Specify initial capacity if the approximate size is known:
Map<String, Integer> map = new HashMap<>(100); // Avoids rehashing
C. Thread Safety
HashMapis not thread-safe.- For concurrent access, use:
ConcurrentHashMap(preferred)Collections.synchronizedMap(new HashMap<>())
8. HashMap vs. Other Map Implementations
| Feature | HashMap | LinkedHashMap | TreeMap |
|---|---|---|---|
| Order | No order | Insertion/access order | Sorted (natural or custom) |
| Null Keys | One | One | Not allowed (throws NullPointerException) |
| Time Complexity | O(1) avg | O(1) avg | O(log n) |
| Use Case | General-purpose | Predictable iteration order | Sorted keys |
9. Best Practices
- Use immutable objects as keys (e.g.,
String,Integer) to prevent accidental changes tohashCode(). - Override
hashCode()andequals()properly in custom key classes. - Specify initial capacity for large maps to avoid rehashing overhead.
- Prefer
ConcurrentHashMapoverCollections.synchronizedMap()for better concurrency. - Avoid using
nullkeys unless necessary—can lead to ambiguousget()results. - Use
Map.of()orMap.copyOf()for small, immutable maps (Java 9+).
10. Common Use Cases
- Caching: Store computed results by key.
- Counting frequencies:
Map<String, Integer>for word counts. - Configuration: Key-value settings.
- Indexing: Map IDs to objects (e.g.,
Map<Integer, User>). - Lookup tables: Fast access by identifier.
Example: Word Frequency Counter
Map<String, Integer> wordCount = new HashMap<>();
String[] words = {"apple", "banana", "apple", "cherry", "banana", "apple"};
for (String word : words) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
// Result: {apple=3, banana=2, cherry=1}
Tip: Use
getOrDefault()(Java 8+) to simplify null checks.
Conclusion
HashMap is a cornerstone of efficient data storage and retrieval in Java, offering fast access to key-value pairs through hashing. Its simplicity, performance, and flexibility make it the go-to choice for most mapping needs. However, to use it effectively, developers must understand its behavior regarding hashing, null handling, thread safety, and performance trade-offs. By following best practices—such as using proper key types, setting appropriate initial capacity, and choosing the right concurrent alternative—programmers can leverage HashMap to build scalable, high-performance applications. Whether caching results, counting occurrences, or managing configurations, mastering HashMap is essential for any Java developer.