In Java Collections, the Map
interface represents a collection designed to store key-value pairs, where each unique key maps to exactly one value. Unlike collections like List
or Set
, which store single elements, a Map
stores associations, making it ideal for scenarios where data retrieval by a key is essential — such as dictionaries, caches, and lookup tables.
The Map<K, V>
interface provides methods to:
put(key, value)
get(key)
containsKey(key)
, containsValue(value)
remove(key)
Each key in a Map
must be unique; if you insert a key that already exists, the previous value is replaced.
Java provides several popular implementations of the Map
interface. Each differs in ordering, thread safety, and performance characteristics.
Implementation | Ordering | Thread Safety | Internal Structure | Notes |
---|---|---|---|---|
HashMap | No guaranteed order | Not synchronized | Hash table with buckets (array + linked lists or trees) | Fastest general-purpose map. Allows null keys and values. |
LinkedHashMap | Insertion order | Not synchronized | Hash table + doubly linked list | Maintains predictable iteration order. Slightly slower than HashMap . |
TreeMap | Sorted by keys (natural or comparator) | Not synchronized | Red-Black tree (self-balancing binary search tree) | Sorted key order. Slower than hash-based maps. No null keys. |
Hashtable | No guaranteed order | Synchronized | Hash table | Legacy class, synchronized by default, replaced by ConcurrentHashMap for concurrency. |
HashMap uses a hash table to store entries. It hashes the key to determine the bucket index where the entry is stored. Each bucket can hold multiple entries if collisions occur, initially stored as a linked list.
Since Java 8, when a bucket’s linked list grows too large (due to many collisions), it converts to a balanced tree (red-black tree) for faster lookup (logarithmic time instead of linear).
Advantages:
O(1)
for put, get)null
key and multiple null
valuesDisadvantages:
Visual analogy: Think of a hash map like a large filing cabinet divided into many drawers (buckets). Each key’s hash code decides which drawer it goes in. Collisions mean multiple files in the same drawer, stored in a linked list or tree.
LinkedHashMap extends HashMap
but maintains a doubly linked list connecting all entries in the order they were inserted. This linked list preserves insertion order during iteration.
Use cases:
Performance: Slightly slower than HashMap
due to maintaining the linked list.
TreeMap stores entries in a red-black tree, a type of self-balancing binary search tree that keeps keys sorted either by their natural ordering (Comparable
) or by a custom Comparator
.
Advantages:
subMap()
, headMap()
, tailMap()
Disadvantages:
O(log n)
for put/get) compared to hash mapsnull
keys (throws NullPointerException
)Visual analogy: Imagine a binary search tree where each node stores a key-value pair. The tree keeps balanced so that retrieval and insertion are efficient and keys are always ordered.
Hashtable
is an older synchronized map implementation, predating Java Collections Framework. It also uses a hash table but:
null
keys or valuesFor thread-safe, high-performance maps, prefer ConcurrentHashMap
.
Feature | HashMap | LinkedHashMap | TreeMap | Hashtable |
---|---|---|---|---|
Ordering | None | Insertion order | Sorted order | None |
Null keys/values allowed | Yes (one null key) | Yes | No (null keys) | No |
Thread safety | No | No | No | Yes (synchronized) |
Performance (avg.) | O(1) | O(1) + linked list overhead | O(log n) | O(1), but synchronized |
Suitable for | General use | When order matters | Sorted key operations | Legacy, synchronized |
Map Interface
├── HashMap (Hash Table + Linked list / Tree for collisions)
├── LinkedHashMap (HashMap + doubly linked list for insertion order)
├── TreeMap (Red-Black Tree for sorted keys)
└── Hashtable (Synchronized Hash Table, legacy)
Understanding the internal workings and characteristics of these core Map
implementations enables you to choose the right one for your needs—balancing speed, ordering guarantees, and concurrency requirements.
At the heart of the Map
interface lies the concept of key-value pairs, which is a powerful way to organize and access data efficiently. In a key-value mapping, each unique key acts like an address or identifier that points to a corresponding value. This structure allows you to quickly retrieve, update, or remove data based on its key, rather than searching through entire collections.
Think of a key-value pair like a dictionary entry in real life: the word is the key, and the definition is the value. You look up a word (the key) to find its meaning (the value). Similarly, in programming, a Map
stores these pairs so you can retrieve a value by providing the key — in constant or near-constant time.
Important:
Maps solve many common problems where quick lookup and association between data are required. Here are some practical use cases:
Caching: When you want to store results of expensive computations or database queries, a map can cache these results with the query as the key and the result as the value. This reduces processing time on repeated calls.
Configuration Settings: Applications often read configuration properties as key-value pairs — for example, "timeout" → 5000
or "theme" → "dark"
. Using a map makes it easy to access and update these settings.
Lookup Tables: If you have a list of user IDs and want to quickly find the associated user details, a map with user ID as key and user info as value is ideal.
Associating Metadata: Maps can store extra information about objects, such as tracking inventory counts ("itemCode" → quantity
) or mapping country codes to country names.
import java.util.HashMap;
import java.util.Map;
public class KeyValueExample {
public static void main(String[] args) {
// Create a Map of employee IDs to names
Map<Integer, String> employeeMap = new HashMap<>();
// Add key-value pairs
employeeMap.put(101, "Alice");
employeeMap.put(102, "Bob");
employeeMap.put(103, "Charlie");
// Retrieve a value using its key
System.out.println("Employee with ID 102: " + employeeMap.get(102));
// Output: Employee with ID 102: Bob
// Attempting to add a duplicate key replaces the old value
employeeMap.put(102, "Barbara");
System.out.println("Updated employee with ID 102: " + employeeMap.get(102));
// Output: Updated employee with ID 102: Barbara
// Values can be duplicated
employeeMap.put(104, "Alice");
System.out.println("Employee with ID 104: " + employeeMap.get(104));
// Output: Employee with ID 104: Alice
}
}
Imagine a library card catalog where each card is identified by a unique call number (the key). The card contains information about the book (the value). When you want to find a book, you look up its call number, and the card tells you exactly where to find it.
This is precisely how Map
structures work: keys uniquely identify the data, making retrieval efficient and intuitive.
This flexible data structure simplifies many programming tasks by turning complex searches into simple key lookups.
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class MapBasicsExample {
public static void main(String[] args) {
// --- Using HashMap ---
// HashMap stores key-value pairs with no guaranteed order.
Map<String, Integer> hashMap = new HashMap<>();
// Adding entries
hashMap.put("apple", 10);
hashMap.put("banana", 20);
hashMap.put("cherry", 30);
// Updating a value for an existing key ("banana")
hashMap.put("banana", 25);
// Removing an entry by key
hashMap.remove("apple");
// Querying values
System.out.println("HashMap contains key 'cherry'? " + hashMap.containsKey("cherry"));
System.out.println("Value for 'banana': " + hashMap.get("banana"));
// Iterating over entries (order not guaranteed)
System.out.println("\nHashMap entries:");
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
// --- Using LinkedHashMap ---
// LinkedHashMap maintains insertion order during iteration.
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
// Adding entries in a different order
linkedHashMap.put("apple", 10);
linkedHashMap.put("banana", 20);
linkedHashMap.put("cherry", 30);
// Updating value for "banana"
linkedHashMap.put("banana", 25);
// Removing "apple"
linkedHashMap.remove("apple");
// Querying values
System.out.println("\nLinkedHashMap contains key 'cherry'? " + linkedHashMap.containsKey("cherry"));
System.out.println("Value for 'banana': " + linkedHashMap.get("banana"));
// Iterating entries (order preserved)
System.out.println("\nLinkedHashMap entries:");
for (Map.Entry<String, Integer> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}
Creating Maps: We create two maps: a HashMap
and a LinkedHashMap
. Both implement Map<String, Integer>
, storing fruit names as keys and quantities as values.
Adding entries: Use put(key, value)
to add key-value pairs. If the key exists, the value is updated.
Updating values: The put
method replaces the old value for a given key. Here, "banana"
’s quantity changes from 20
to 25
.
Removing entries: remove(key)
deletes the key-value pair associated with the specified key.
Querying contents:
containsKey(key)
checks if the key exists.get(key)
retrieves the value for a key.Iteration and Ordering Differences: When iterating over the HashMap
entries, the output order is unpredictable because HashMap
does not preserve insertion order. On the other hand, the LinkedHashMap
maintains the order in which keys were inserted, so iteration prints entries in the same order they were added (except for the removed key "apple"
).
HashMap contains key 'cherry'? true
Value for 'banana': 25
HashMap entries:
banana = 25
cherry = 30
LinkedHashMap contains key 'cherry'? true
Value for 'banana': 25
LinkedHashMap entries:
banana = 25
cherry = 30
This example helps beginners understand basic Map
operations and the important distinction between unordered HashMap
and insertion-ordered LinkedHashMap
. Using these fundamentals, you can choose the right map implementation depending on whether order matters for your use case.