NOTE
LinkedHashMap
What LinkedHashMap is, insertion-order and access-order usage, and source analysis of construction, put, get, contains, remove, and iteration.
This is a historical learning note and may contain outdated or incomplete understanding.
1. What It Is
- Implemented using a doubly linked list + HashMap (array + linked list + red-black tree).
- Compared with HashMap, it preserves order.
- The iteration output order can be:
- insertion order;
- access order (LRU).
- The iteration output order can be:
2. Usage
- Output in insertion order:
public class LinkedHashMapTest
{
public static void main(String[] args)
{
LinkedHashMap<String,Object> map = new LinkedHashMap<>();
map.put("name","zsk");
map.put("age",24);
map.put("height", 172L);
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry<String, Object> entry = iterator.next();
/* The output order is the insertion order:
* name=zsk
age=24
height=172
* */
System.out.println(entry);
}
// The output above is the same as this one
for (Map.Entry<String, Object> entry : map.entrySet())
{
/* The output order is the insertion order:
* name=zsk
age=24
height=172
* */
System.out.println(entry);
}
System.out.println(map.containsKey("name"));//true
System.out.println(map.get("name"));//zsk
map.remove("name");
System.out.println(map.containsKey("name"));//false
}
}
- Output in access order:
public class LinkedHashMapTest
{
public static void main(String[] args)
{
LinkedHashMap<String, Object> map = new LinkedHashMap<>(2, 0.75F, true);
map.put("1", "a");
map.put("2", "b");
map.put("3", "c");
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry<String, Object> entry = iterator.next();
// 1=a
// 2=b
// 3=c
System.out.println(entry);
}
// The output above is the same as this one
for (Map.Entry<String, Object> entry : map.entrySet())
{
// 1=a
// 2=b
// 3=c
System.out.println(entry);
}
System.out.println(map.get("1"));// Access 1, so the node containing 1 is moved to the end of the list
for (Map.Entry<String, Object> entry : map.entrySet())
{
// The most recently accessed node is moved to the end of the list
// 2=b
// 3=c
// 1=a
System.out.println(entry);
}
}
}
3. Implementation
3.1. UML

It extends HashMap and is cloneable and serializable.
3.2. Constructor
public class LinkedHashMap<K,V>
extends HashMap<K,V>// Extends HashMap
implements Map<K,V>
{
// Nodes in the linked list. Doubly linked list
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
// Head and tail nodes of the doubly linked list
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
// true means maintain access order; false means maintain insertion order
final boolean accessOrder;
public LinkedHashMap() {
// Call HashMap's constructor
super();
accessOrder = false;
}
}
LinkedHashMap extends HashMap, so methods such as get, set, and remove call HashMap methods. LinkedHashMap also enhances HashMap’s Node, defines its own Entry, and adds a doubly linked list.
3.3. put
It essentially calls HashMap’s put method to insert a new node or replace a value, with some additional operations.
HashMap.put
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
// LinkedHashMap overrides newNode
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// The node already exists; only update value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// Need to maintain the order in the doubly linked list
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
// When removeEldestEntry returns true, delete the head node [least recently accessed node]
afterNodeInsertion(evict);
return null;
}
Pay attention to the following points:
- Line 12: LinkedHashMap overrides
newNode. - Lines 35-42: after updating a value, the order in the doubly linked list must be maintained.
- Line 48: whether inserting a new node or updating a value, it may be necessary to delete the head node according to the situation (whether
removeEldestEntryreturns true).
3.3.1. Create LinkedHashMap’s Enhanced Node — Entry [Both a Node-Array Node and a Doubly Linked List Node]
LinkedHashMap.newNode
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
// Create LinkedHashMap's enhanced Entry
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
// Insert it at the end of the linked list
linkNodeLast(p);
return p;
}
3.3.1.1. Insert the Node at the End of the Doubly Linked List When It Is Created
linkNodeLast
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
// Insert the current node at the end of the linked list
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
3.3.2. For a Node Updated by put (Not Newly Inserted, Only value Updated), Maintain the Doubly Linked List Order [Output Order]
// The node already exists; only update value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// Need to maintain the order in the doubly linked list
afterNodeAccess(e);
return oldValue;
}
3.3.2.1. Move This Node to the End of the Doubly Linked List
afterNodeAccess
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
// Access order is enabled and the current node is not the tail [that is, it is not a newly inserted node]
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// The following operations move the current node to the end of the doubly linked list
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
3.3.3. After put (Whether Newly Inserted or value Updated), Determine Whether to Delete the Head Node [Least Recently Accessed Node]
afterNodeInsertion
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
// When removeEldestEntry returns true
// [for example, in an LRU algorithm, delete the least recently accessed node after capacity is exceeded]
if (evict && (first = head) != null && removeEldestEntry(first)) {
// Delete the head node
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
3.4. get
It essentially calls HashMap’s getNode to obtain the node and then calls LinkedHashMap’s afterNodeAccess to place the currently accessed node at the end of the linked list.
LinkedHashMap.get
public V get(Object key) {
Node<K,V> e;
// Call HashMap.getNode to obtain the node
if ((e = getNode(hash(key), key)) == null)
return null;
// If access order is enabled
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
- Lines 7-8: if access order is enabled, call LinkedHashMap’s
afterNodeAccessto move the currently accessed node to the end of the doubly linked list (the newest node).
3.4.1. After get, Maintain the Doubly Linked List Order [Output Order]
// If access order is enabled
if (accessOrder)
afterNodeAccess(e);
3.4.1.1. Move the Currently Accessed Node to the End of the Linked List
LinkedHashMap.afterNodeAccess
Just like the put operation, move the currently accessed node to the end of the linked list.
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
// p is the current node, a is the next node, b is the previous node
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// The previous node's next points to the next node
p.after = null;
if (b == null)
head = a;
else
b.after = a;
// The next node's prev points to the previous node
if (a != null)
a.before = b;
else
last = b;
// The current node's prev points to the tail, and the tail's next points to the current node
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
// Update the tail to the current node
tail = p;
++modCount;
}
}
3.5. containsKey
It simply calls HashMap’s containsKey.
public boolean containsKey(Object key) {
// Still HashMap.getNode; nothing special here
return getNode(hash(key), key) != null;
}
3.6. containsValue
It overrides HashMap’s method and is more efficient.
public boolean containsValue(Object value) {
// Traverse the doubly linked list, O(N), unlike HashMap's O(N2)
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
V v = e.value;
if (v == value || (value != null && value.equals(v)))
return true;
}
return false;
}
3.7. remove
It essentially calls HashMap’s remove to delete the node, and then calls LinkedHashMap’s afterNodeRemoval to remove the current node from the doubly linked list.
HashMap.remove
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
// After deleting the node, maintain the doubly linked list
afterNodeRemoval(node);
return node;
}
}
return null;
}
3.7.1. After remove, Delete the Node from the Doubly Linked List
LinkedHashMap.afterNodeRemoval
void afterNodeRemoval(Node<K,V> e) { // unlink
// The following operations delete the current node from the doubly linked list
// p is the current deleted node, b is p's previous node, and a is p's next node
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
// Modify the previous node's next pointer
if (b == null)
head = a;
else
b.after = a;
// Modify the next node's prev pointer
if (a == null)
tail = b;
else
a.before = b;
}
3.8. entrySet
3.8.1. Code to Study
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
LinkedHashMap.entrySet
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
// When is this entrySet field set?
// Return LinkedEntrySet
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
Calling map.entrySet() returns LinkedEntrySet, as follows.
3.8.2. LinkedHashMap.entrySet() Returns LinkedEntrySet
LinkedEntrySet
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
// The iterator is LinkedEntryIterator
return new LinkedEntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED |
Spliterator.DISTINCT);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
Then calling LinkedEntrySet.iterator returns LinkedEntryIterator.
3.8.3. LinkedHashMap.entrySet().iterator() Returns LinkedEntryIterator
final class LinkedEntryIterator extends LinkedHashIterator// Extends LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
// Override next and call LinkedHashIterator.nextNode
public final Map.Entry<K,V> next() { return nextNode(); }
}
This LinkedEntryIterator extends LinkedHashIterator, as follows.
3.8.3.1. LinkedEntryIterator Extends LinkedHashIterator
LinkedHashIterator
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
LinkedHashIterator() {
// Start traversal from the head of the linked list
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
return next != null;
}
// next calls this method
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
// The next node after the current node in the linked list
current = e;
next = e.after;
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
// HashMap.removeNode
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
It can be seen that iteration outputs entries from head to tail, meaning older nodes are printed first, then newer nodes.
4. Summary
It is implemented as HashMap + a doubly linked list.
Iterating over LinkedHashMap means looping from the head of the internally maintained doubly linked list.
The order of nodes in the doubly linked list is updated during LinkedHashMap’s get and put operations, so that it can support output by insertion order or by access order.
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub