LinkedHashMap
LinkedHashMap is a HashMap that remembers the order in which you added entries. Whenever you need a key-value store with predictable iteration order, LinkedHashMap is your go-to choice — with almost no performance penalty over a plain HashMap.
What is LinkedHashMap?
LinkedHashMap<K, V> lives in java.util and extends HashMap<K, V>. It implements the Map interface, so every entry is a key → value pair, keys are unique, and one null key is allowed.
The critical difference from HashMap is that LinkedHashMap maintains a doubly-linked list running through all its entries. This list defines the iteration order, which can be either:
- Insertion order (default) — entries come out in the order you called
put() - Access order — entries are reordered so the most-recently-accessed entry is last (useful for LRU caches — more on this below)
import java.util.LinkedHashMap;
public class BasicLinkedHashMap {
public static void main(String[] args) {
LinkedHashMap<String, Integer> scores = new LinkedHashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Carol", 92);
System.out.println(scores);
System.out.println(scores.get("Bob"));
}
}
Output:
{Alice=95, Bob=87, Carol=92}
87
Notice how the output keeps the exact insertion order — unlike a plain HashMap, which gives no such guarantee.
Creating a LinkedHashMap
Default Constructor (insertion order)
LinkedHashMap<String, String> capitals = new LinkedHashMap<>();
capitals.put("France", "Paris");
capitals.put("Japan", "Tokyo");
capitals.put("Brazil", "Brasília");
for (var entry : capitals.entrySet()) {
System.out.println(entry.getKey() + " → " + entry.getValue());
}
Output:
France → Paris
Japan → Tokyo
Brazil → Brasília
With Initial Capacity and Load Factor
// capacity 16, load factor 0.75 (same defaults as HashMap)
LinkedHashMap<Integer, String> map = new LinkedHashMap<>(16, 0.75f);
Access-Order Mode
Pass true as the third constructor argument to enable access order:
LinkedHashMap<String, Integer> accessMap =
new LinkedHashMap<>(16, 0.75f, true); // accessOrder = true
accessMap.put("a", 1);
accessMap.put("b", 2);
accessMap.put("c", 3);
accessMap.get("a"); // accessing "a" moves it to the tail
System.out.println(accessMap);
Output:
{b=2, c=3, a=1}
"a" was accessed last, so it appears at the end. This ordering is the foundation for building an LRU (Least-Recently-Used) cache.
Common Methods
LinkedHashMap inherits all HashMap methods — there is no new API. The methods you will use most:
| Method | Description |
|---|---|
put(K key, V value) | Inserts or updates an entry |
get(Object key) | Returns the value for the key, or null |
getOrDefault(key, def) | Returns value or a default if absent |
containsKey(Object key) | true if the key exists |
containsValue(Object value) | true if the value exists |
remove(Object key) | Removes the entry for that key |
size() | Number of entries |
isEmpty() | true when empty |
clear() | Removes all entries |
keySet() | Set of keys (insertion/access ordered) |
values() | Collection of values (ordered) |
entrySet() | Set of Map.Entry objects (ordered) |
putIfAbsent(K, V) | Inserts only when key is absent |
computeIfAbsent(K, fn) | Inserts computed value if key is absent |
import java.util.LinkedHashMap;
public class LinkedHashMapMethods {
public static void main(String[] args) {
LinkedHashMap<String, Integer> stock = new LinkedHashMap<>();
stock.put("apples", 50);
stock.put("bananas", 30);
stock.put("cherries", 10);
// getOrDefault
System.out.println(stock.getOrDefault("dates", 0)); // 0
// putIfAbsent
stock.putIfAbsent("apples", 999); // ignored — key exists
stock.putIfAbsent("dates", 25); // inserted
// iterate in insertion order
stock.forEach((item, qty) ->
System.out.println(item + ": " + qty));
}
}
Output:
0
apples: 50
bananas: 30
cherries: 10
dates: 25
Iterating a LinkedHashMap
Because iteration order is guaranteed, LinkedHashMap is one of the easiest maps to reason about when printing or processing entries.
import java.util.LinkedHashMap;
import java.util.Map;
public class IterateLinkedHashMap {
public static void main(String[] args) {
LinkedHashMap<String, Double> prices = new LinkedHashMap<>();
prices.put("coffee", 3.50);
prices.put("tea", 2.00);
prices.put("juice", 4.25);
// 1. entrySet (most common)
for (Map.Entry<String, Double> e : prices.entrySet()) {
System.out.printf("%-10s $%.2f%n", e.getKey(), e.getValue());
}
// 2. forEach (Java 8+)
System.out.println("---");
prices.forEach((k, v) -> System.out.println(k + " = " + v));
// 3. keySet
System.out.println("---");
for (String key : prices.keySet()) {
System.out.println(key);
}
}
}
Output:
coffee $3.50
tea $2.00
juice $4.25
---
coffee = 3.50
tea = 2.00
juice = 4.25
---
coffee
tea
juice
Building an LRU Cache
The most powerful real-world use of LinkedHashMap is a self-maintaining Least-Recently-Used cache. Override removeEldestEntry() to evict the oldest entry once the cache exceeds your chosen capacity.
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true); // accessOrder = true
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
public static void main(String[] args) {
LRUCache<Integer, String> cache = new LRUCache<>(3);
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
cache.get(1); // access "one" → moves to tail
cache.put(4, "four"); // size exceeds 3 → evicts eldest ("two")
System.out.println(cache); // {three=three, one=one, four=four}
}
}
Output:
{3=three, 1=one, 4=four}
Tip: For production-grade caches, consider Guava’s
CacheBuilderor Caffeine, which offer TTL, statistics, and thread safety. TheLinkedHashMapapproach is single-threaded only.
Under the Hood
Understanding how LinkedHashMap works internally helps you make better decisions about when to use it and how to tune it.
Data Structure
LinkedHashMap reuses the hash table (array of buckets + chaining/tree) from HashMap — every entry lookup is still O(1) average. On top of that, each Entry node carries two additional pointers: before and after. These form a doubly-linked list that is maintained separately from the bucket structure.
Hash table (buckets):
[0] → Entry("Bob", 87)
[2] → Entry("Alice", 95)
[5] → Entry("Carol", 92)
Doubly-linked list (insertion order):
head ↔ Entry("Alice") ↔ Entry("Bob") ↔ Entry("Carol") ↔ tail
When you call put(), a new node is appended to the tail of the linked list. When you call remove(), the node is unlinked from both the bucket chain and the doubly-linked list in O(1).
Memory Overhead
Each entry carries two extra object references (before / after) compared to a plain HashMap entry — roughly 16 extra bytes per entry on a 64-bit JVM. For most applications this is negligible, but it matters when you store millions of tiny entries.
Access Order Mechanics
In access-order mode, every call to get(), put() (on an existing key), or getOrDefault() calls the internal afterNodeAccess() method, which unlinks the accessed node and reattaches it at the tail of the doubly-linked list. This is an O(1) operation, so access-order mode adds almost no overhead per lookup.
Thread Safety
LinkedHashMap is not thread-safe. If multiple threads access the same instance concurrently and at least one modifies it, you must synchronize externally:
Map<String, Integer> syncMap =
Collections.synchronizedMap(new LinkedHashMap<>());
For highly concurrent workloads, look at ConcurrentHashMap instead (though it does not preserve insertion order).
LinkedHashMap vs HashMap vs TreeMap
| Feature | HashMap | LinkedHashMap | TreeMap |
|---|---|---|---|
| Ordering | None (random) | Insertion or access order | Natural / Comparator sort |
| Null keys | 1 allowed | 1 allowed | Not allowed |
| Performance (get/put) | O(1) avg | O(1) avg | O(log n) |
| Memory overhead | Lowest | Slightly higher | Higher |
| Thread-safe | No | No | No |
| Best for | Fast lookups, no order needed | Predictable order, LRU cache | Sorted iteration, range queries |
Note: If you just need to iterate a map in the order it was populated — for example, to render a form or build a JSON response with stable field order —
LinkedHashMapis the cleanest solution.
Practical Example: Word Frequency Counter (Ordered)
import java.util.LinkedHashMap;
public class WordFrequency {
public static void main(String[] args) {
String sentence = "the quick brown fox jumps over the lazy fox";
LinkedHashMap<String, Integer> freq = new LinkedHashMap<>();
for (String word : sentence.split(" ")) {
freq.merge(word, 1, Integer::sum);
}
freq.forEach((word, count) ->
System.out.printf("%-10s %d%n", word, count));
}
}
Output:
the 2
quick 1
brown 1
fox 2
jumps 1
over 1
lazy 1
The words appear in the order they were first encountered, while their counts accumulate correctly. The merge() method (Java 8+) handles both insertion and update in one call.
Related Topics
- HashMap — the parent class; understand its hash table internals before diving into
LinkedHashMap - Working of HashMap (Internals) — deep-dive into hashing, bucket collisions, and tree-ification in Java 8+
- TreeMap — when you need entries sorted by key rather than insertion order
- LinkedHashSet — the Set counterpart that preserves insertion order without duplicates
- Map Interface — the common contract that
LinkedHashMap,HashMap, andTreeMapall implement - Concurrent Collections — thread-safe map alternatives when multiple threads share state