Overview
LinkedHashMap maintains a doubly-linked list running through all its entries, which defines the iteration ordering. By default, it maintains insertion order, but can also be configured to maintain access order.
Basic Insertion Order Example
import java.util.*; public class LinkedHashMapInsertionOrder { public static void main(String[] args) { // Creating LinkedHashMap with default insertion order LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>(); // Adding elements - order will be preserved linkedHashMap.put("Apple", 1); linkedHashMap.put("Banana", 2); linkedHashMap.put("Orange", 3); linkedHashMap.put("Mango", 4); linkedHashMap.put("Grapes", 5); System.out.println("LinkedHashMap (Insertion Order):"); linkedHashMap.forEach((key, value) -> System.out.println(key + " = " + value)); // Compare with HashMap (no order guarantee) HashMap<String, Integer> hashMap = new HashMap<>(); hashMap.put("Apple", 1); hashMap.put("Banana", 2); hashMap.put("Orange", 3); hashMap.put("Mango", 4); hashMap.put("Grapes", 5); System.out.println("\nHashMap (No Order Guarantee):"); hashMap.forEach((key, value) -> System.out.println(key + " = " + value)); } } Output:
LinkedHashMap (Insertion Order): Apple = 1 Banana = 2 Orange = 3 Mango = 4 Grapes = 5 HashMap (No Order Guarantee): Orange = 3 Apple = 1 Grapes = 5 Banana = 2 Mango = 4
LinkedHashMap Constructors
import java.util.*; public class LinkedHashMapConstructors { public static void main(String[] args) { // 1. Default constructor - insertion order LinkedHashMap<String, Integer> map1 = new LinkedHashMap<>(); map1.put("A", 1); map1.put("B", 2); map1.put("C", 3); System.out.println("Default (Insertion Order): " + map1.keySet()); // 2. With initial capacity LinkedHashMap<String, Integer> map2 = new LinkedHashMap<>(16); map2.put("X", 10); map2.put("Y", 20); map2.put("Z", 30); System.out.println("With Capacity: " + map2.keySet()); // 3. With initial capacity and load factor LinkedHashMap<String, Integer> map3 = new LinkedHashMap<>(16, 0.75f); map3.put("P", 100); map3.put("Q", 200); map3.put("R", 300); System.out.println("With Capacity & Load Factor: " + map3.keySet()); // 4. With initial capacity, load factor, and access order LinkedHashMap<String, Integer> map4 = new LinkedHashMap<>( 16, 0.75f, true); // true for access order map4.put("One", 1); map4.put("Two", 2); map4.put("Three", 3); System.out.println("Before access: " + map4.keySet()); // Access some elements to change order map4.get("One"); map4.get("Two"); System.out.println("After access: " + map4.keySet()); // 5. From existing map Map<String, Integer> existingMap = new HashMap<>(); existingMap.put("First", 1); existingMap.put("Second", 2); existingMap.put("Third", 3); LinkedHashMap<String, Integer> map5 = new LinkedHashMap<>(existingMap); System.out.println("From existing map: " + map5.keySet()); } } Insertion Order vs Access Order
Insertion Order (Default)
import java.util.*; public class InsertionOrderExample { public static void main(String[] args) { LinkedHashMap<String, String> insertionOrderMap = new LinkedHashMap<>(); insertionOrderMap.put("Z", "Zebra"); insertionOrderMap.put("A", "Apple"); insertionOrderMap.put("M", "Monkey"); insertionOrderMap.put("B", "Banana"); System.out.println("Insertion Order Map:"); insertionOrderMap.forEach((k, v) -> System.out.println(k + " -> " + v)); // Even after modifications, insertion order is maintained insertionOrderMap.remove("M"); insertionOrderMap.put("C", "Cat"); insertionOrderMap.put("M", "Modified Monkey"); // Re-insert System.out.println("\nAfter modifications:"); insertionOrderMap.forEach((k, v) -> System.out.println(k + " -> " + v)); } } Output:
Insertion Order Map: Z -> Zebra A -> Apple M -> Monkey B -> Banana After modifications: Z -> Zebra A -> Apple B -> Banana C -> Cat M -> Modified Monkey
Access Order Mode
import java.util.*; public class AccessOrderExample { public static void main(String[] args) { // Third parameter 'true' enables access order LinkedHashMap<String, String> accessOrderMap = new LinkedHashMap<>( 16, 0.75f, true); accessOrderMap.put("A", "Apple"); accessOrderMap.put("B", "Banana"); accessOrderMap.put("C", "Cherry"); accessOrderMap.put("D", "Date"); System.out.println("Initial order: " + accessOrderMap.keySet()); // Access some elements accessOrderMap.get("B"); // Access B System.out.println("After accessing B: " + accessOrderMap.keySet()); accessOrderMap.get("A"); // Access A System.out.println("After accessing A: " + accessOrderMap.keySet()); accessOrderMap.put("E", "Elderberry"); // Put also counts as access System.out.println("After adding E: " + accessOrderMap.keySet()); accessOrderMap.get("C"); // Access C System.out.println("After accessing C: " + accessOrderMap.keySet()); } } Output:
Initial order: [A, B, C, D] After accessing B: [A, C, D, B] After accessing A: [C, D, B, A] After adding E: [C, D, B, A, E] After accessing C: [D, B, A, E, C]
Real-World Use Cases
1. LRU (Least Recently Used) Cache
import java.util.*; class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); // Access order = true this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > capacity; } public void displayCache() { System.out.println("Cache contents (most recent first):"); List<K> keys = new ArrayList<>(keySet()); Collections.reverse(keys); // Reverse to show most recent first for (K key : keys) { System.out.println(key + " -> " + get(key)); } System.out.println(); } } public class LRUCacheExample { public static void main(String[] args) { LRUCache<String, String> cache = new LRUCache<>(3); cache.put("User1", "Data1"); cache.put("User2", "Data2"); cache.put("User3", "Data3"); cache.displayCache(); cache.get("User1"); // Access User1 - makes it most recent cache.displayCache(); cache.put("User4", "Data4"); // This will remove eldest entry (User2) cache.displayCache(); cache.put("User5", "Data5"); // This will remove eldest entry (User3) cache.displayCache(); } } Output:
Cache contents (most recent first): User3 -> Data3 User2 -> Data2 User1 -> Data1 Cache contents (most recent first): User1 -> Data1 User3 -> Data3 User2 -> Data2 Cache contents (most recent first): User4 -> Data4 User1 -> Data1 User3 -> Data3 Cache contents (most recent first): User5 -> Data5 User4 -> Data4 User1 -> Data1
2. Maintaining Insertion Order for Configuration
import java.util.*; public class ConfigurationManager { private LinkedHashMap<String, String> config = new LinkedHashMap<>(); public void setConfig(String key, String value) { config.put(key, value); } public void displayConfigInOrder() { System.out.println("Configuration (in setting order):"); config.forEach((key, value) -> System.out.println(key + " = " + value)); } public static void main(String[] args) { ConfigurationManager manager = new ConfigurationManager(); // Set configuration in specific order manager.setConfig("database.host", "localhost"); manager.setConfig("database.port", "5432"); manager.setConfig("app.name", "MyApp"); manager.setConfig("app.version", "1.0.0"); manager.setConfig("logging.level", "DEBUG"); manager.displayConfigInOrder(); // Update existing config - order remains based on initial insertion manager.setConfig("database.port", "3306"); manager.setConfig("app.name", "MyUpdatedApp"); System.out.println("\nAfter updates:"); manager.displayConfigInOrder(); } } 3. Recent Items Tracker
import java.util.*; class RecentItemsTracker<K> { private LinkedHashMap<K, Long> recentItems; private final int maxSize; public RecentItemsTracker(int maxSize) { this.maxSize = maxSize; this.recentItems = new LinkedHashMap<K, Long>(maxSize, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<K, Long> eldest) { return size() > maxSize; } }; } public void addItem(K item) { recentItems.put(item, System.currentTimeMillis()); } public void accessItem(K item) { if (recentItems.containsKey(item)) { recentItems.get(item); // Just access to update order } } public List<K> getRecentItems() { return new ArrayList<>(recentItems.keySet()); } public List<K> getRecentItemsMostRecentFirst() { List<K> items = new ArrayList<>(recentItems.keySet()); Collections.reverse(items); return items; } } public class RecentItemsExample { public static void main(String[] args) { RecentItemsTracker<String> tracker = new RecentItemsTracker<>(5); tracker.addItem("Item1"); tracker.addItem("Item2"); tracker.addItem("Item3"); tracker.addItem("Item4"); tracker.addItem("Item5"); System.out.println("Initial items: " + tracker.getRecentItems()); tracker.accessItem("Item2"); // Make Item2 most recent System.out.println("After accessing Item2: " + tracker.getRecentItems()); tracker.addItem("Item6"); // This should remove Item1 (eldest) System.out.println("After adding Item6: " + tracker.getRecentItems()); System.out.println("Most recent first: " + tracker.getRecentItemsMostRecentFirst()); } } Advanced Operations
Iteration Order Preservation
import java.util.*; public class IterationOrderExample { public static void main(String[] args) { LinkedHashMap<Integer, String> orderedMap = new LinkedHashMap<>(); // Add elements in specific order for (int i = 10; i > 0; i--) { orderedMap.put(i, "Value" + i); } System.out.println("Iteration order matches insertion order:"); // Entry set iteration System.out.println("Entry Set:"); for (Map.Entry<Integer, String> entry : orderedMap.entrySet()) { System.out.println(entry.getKey() + " -> " + entry.getValue()); } // Key set iteration System.out.println("\nKey Set:"); for (Integer key : orderedMap.keySet()) { System.out.println(key); } // Values iteration System.out.println("\nValues:"); for (String value : orderedMap.values()) { System.out.println(value); } } } Copy Preservation
import java.util.*; public class CopyPreservationExample { public static void main(String[] args) { LinkedHashMap<String, Integer> original = new LinkedHashMap<>(); original.put("First", 1); original.put("Second", 2); original.put("Third", 3); original.put("Fourth", 4); System.out.println("Original: " + original.keySet()); // Copy constructor preserves order LinkedHashMap<String, Integer> copy = new LinkedHashMap<>(original); System.out.println("Copy: " + copy.keySet()); // PutAll preserves order LinkedHashMap<String, Integer> anotherCopy = new LinkedHashMap<>(); anotherCopy.putAll(original); System.out.println("PutAll Copy: " + anotherCopy.keySet()); // Sub operations may not preserve full order in some Java versions LinkedHashMap<String, Integer> subMap = new LinkedHashMap<>(); original.entrySet().stream() .limit(2) .forEach(entry -> subMap.put(entry.getKey(), entry.getValue())); System.out.println("Sub Map: " + subMap.keySet()); } } Performance Characteristics
import java.util.*; public class PerformanceComparison { public static void main(String[] args) { int size = 100000; // HashMap performance long startTime = System.currentTimeMillis(); Map<Integer, String> hashMap = new HashMap<>(); for (int i = 0; i < size; i++) { hashMap.put(i, "Value" + i); } long hashMapTime = System.currentTimeMillis() - startTime; // LinkedHashMap performance (insertion order) startTime = System.currentTimeMillis(); Map<Integer, String> linkedHashMap = new LinkedHashMap<>(); for (int i = 0; i < size; i++) { linkedHashMap.put(i, "Value" + i); } long linkedHashMapTime = System.currentTimeMillis() - startTime; // LinkedHashMap performance (access order) startTime = System.currentTimeMillis(); Map<Integer, String> accessOrderMap = new LinkedHashMap<>(16, 0.75f, true); for (int i = 0; i < size; i++) { accessOrderMap.put(i, "Value" + i); } long accessOrderTime = System.currentTimeMillis() - startTime; System.out.println("HashMap put time: " + hashMapTime + "ms"); System.out.println("LinkedHashMap (insertion) put time: " + linkedHashMapTime + "ms"); System.out.println("LinkedHashMap (access) put time: " + accessOrderTime + "ms"); // Iteration performance startTime = System.currentTimeMillis(); for (String value : hashMap.values()) { // Just iterating } long hashMapIteration = System.currentTimeMillis() - startTime; startTime = System.currentTimeMillis(); for (String value : linkedHashMap.values()) { // Just iterating } long linkedHashMapIteration = System.currentTimeMillis() - startTime; System.out.println("\nHashMap iteration time: " + hashMapIteration + "ms"); System.out.println("LinkedHashMap iteration time: " + linkedHashMapIteration + "ms"); } } Key Points Summary
- Insertion Order: Default mode, maintains order in which entries were added
- Access Order: When enabled (third constructor parameter = true), maintains order based on access pattern
- Performance: Slightly slower than HashMap due to maintaining linked list
- Memory: Uses more memory than HashMap due to linked list overhead
- Thread Safety: Not synchronized - use
Collections.synchronizedMap()if needed - Use Cases:
- LRU Caches
- Maintaining configuration order
- Recent items tracking
- Any scenario where iteration order matters
Best Practices
import java.util.*; public class LinkedHashMapBestPractices { public static void main(String[] args) { // 1. Use appropriate initial capacity LinkedHashMap<String, String> map1 = new LinkedHashMap<>(100); // 2. Use access order for LRU caches LinkedHashMap<String, String> lruCache = new LinkedHashMap<>( 16, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<String, String> eldest) { return size() > 1000; } }; // 3. For thread safety Map<String, String> synchronizedMap = Collections.synchronizedMap(new LinkedHashMap<>()); // 4. Preserve order during copy LinkedHashMap<String, Integer> original = new LinkedHashMap<>(); original.put("A", 1); original.put("B", 2); LinkedHashMap<String, Integer> copy = new LinkedHashMap<>(original); // 5. Iterate efficiently for (Map.Entry<String, Integer> entry : copy.entrySet()) { System.out.println(entry.getKey() + " = " + entry.getValue()); } } } LinkedHashMap is the perfect choice when you need HashMap's performance characteristics combined with predictable iteration order, making it invaluable for many real-world applications.