Web Analytics

Java HashMap

Intermediate ~20 min read

A HashMap is Java's most used key-value data structure, providing O(1) average time for get and put operations. It's similar to Python's Dictionary or JavaScript's Object/Map.

Java HashMap Internal Structure Diagram

How HashMap Works

HashMap uses a hash table with bucket-based storage:

  1. Key's hashCode() determines which bucket
  2. Entry (key-value pair) is stored in that bucket
  3. Collisions are handled with linked lists (or trees in Java 8+)
  4. On retrieval, equals() finds the exact key
Java 8+ Optimization: When a bucket has more than 8 entries, the linked list converts to a red-black tree for O(log n) lookup instead of O(n).

Key Characteristics

Feature HashMap
Key uniqueness Keys must be unique (duplicates overwrite)
Value uniqueness Values can be duplicated
Null keys One null key allowed
Null values Multiple null values allowed
Order No guaranteed order
Thread-safe No (use ConcurrentHashMap)

Creating a HashMap

import java.util.HashMap;
import java.util.Map;

// Standard way
Map<String, Integer> ages = new HashMap<>();

// With initial capacity
Map<String, String> bigMap = new HashMap<>(1000);

// With initial capacity and load factor
Map<String, String> map = new HashMap<>(16, 0.75f);

// Java 9+ factory method (immutable)
Map<String, Integer> immutable = Map.of(
    "Alice", 30,
    "Bob", 25
);

Essential Operations

Map<String, Integer> scores = new HashMap<>();

// Adding entries
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Alice", 98);  // Overwrites! Returns old value (95)

// Getting values
Integer aliceScore = scores.get("Alice");     // 98
Integer unknown = scores.get("Unknown");      // null

// Safe get with default
Integer score = scores.getOrDefault("Charlie", 0); // 0

// Check existence
boolean hasAlice = scores.containsKey("Alice");    // true
boolean has95 = scores.containsValue(95);          // false (was overwritten)

// Remove
Integer removed = scores.remove("Bob");  // Returns 87

// Size
int size = scores.size();
Output
Click Run to execute your code

Iterating Through HashMap

Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Charlie", 92);

// 1. Iterate over entries (most common)
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// 2. Iterate over keys only
for (String name : scores.keySet()) {
    System.out.println(name);
}

// 3. Iterate over values only
for (Integer score : scores.values()) {
    System.out.println(score);
}

// 4. Java 8+ forEach with lambda
scores.forEach((name, score) ->
    System.out.println(name + " scored " + score)
);

Advanced Operations (Java 8+)

Map<String, Integer> wordCount = new HashMap<>();

// Compute if absent (great for counters/caches)
wordCount.computeIfAbsent("hello", k -> 0);

// Merge (perfect for counting)
String[] words = {"apple", "banana", "apple", "cherry", "apple"};
for (String word : words) {
    wordCount.merge(word, 1, Integer::sum);
}
// {apple=3, banana=1, cherry=1}

// putIfAbsent (only add if key missing)
wordCount.putIfAbsent("date", 1);

// Replace (only if key exists)
wordCount.replace("apple", 5);

// Conditional replace
wordCount.replace("apple", 5, 10);  // Only if value is 5

// Remove only if value matches
wordCount.remove("banana", 1);  // Removes banana

// Compute (update based on current value)
wordCount.compute("apple", (k, v) -> v == null ? 1 : v + 1);

Common Patterns

// Pattern 1: Counting frequency
Map<Character, Integer> charCount = new HashMap<>();
for (char c : "hello".toCharArray()) {
    charCount.merge(c, 1, Integer::sum);
}

// Pattern 2: Grouping
Map<String, List<String>> byLength = new HashMap<>();
for (String word : Arrays.asList("cat", "dog", "elephant", "ant")) {
    byLength.computeIfAbsent(
        word.length() > 3 ? "long" : "short",
        k -> new ArrayList<>()
    ).add(word);
}

// Pattern 3: Caching/Memoization
Map<Integer, Long> cache = new HashMap<>();
long fibonacci(int n) {
    return cache.computeIfAbsent(n, k ->
        k <= 1 ? k : fibonacci(k-1) + fibonacci(k-2)
    );
}

When to Use Which Map?

Map Type Key Order Performance Use When
HashMap None O(1) Best general-purpose choice
LinkedHashMap Insertion order O(1) Need predictable iteration
TreeMap Sorted by key O(log n) Need sorted keys
ConcurrentHashMap None O(1) Multi-threaded access

Summary

  • HashMap provides O(1) performance for get/put operations
  • Keys must be unique; values can duplicate
  • Use entrySet() for efficient iteration
  • Java 8+ methods: merge(), computeIfAbsent(), forEach()
  • Not thread-safe - use ConcurrentHashMap for concurrent access