LRU 结构 LinkedHashMap:HashMap+双向链表的完美结合
LinkedHashMap = HashMap + 双向链表,在HashMap的基础上增加了插入/访问顺序的维护能力。
核心结构组件
1. Entry节点设计
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);}
}
设计亮点:
- 继承HashMap.Node,保持哈希表功能
- 添加
before/after
指针维护插入顺序 - 一个节点同时参与两种数据结构:哈希表的链表/红黑树 + 全局双向链表
2. 链表头尾指针
transient LinkedHashMap.Entry<K,V> head; // 最老的节点
transient LinkedHashMap.Entry<K,V> tail; // 最新的节点
三种排序模式机制
1. 插入顺序模式(默认)
final boolean accessOrder = false; // 插入顺序
2. 访问顺序模式
final boolean accessOrder = true; // LRU语义
3. 动态定位模式(Java 21新特性)
static final int PUT_NORM = 0; // 正常模式
static final int PUT_FIRST = 1; // 插入到头部
static final int PUT_LAST = 2; // 插入到尾部
transient int putMode = PUT_NORM;
深入分析有意思的设计点
1. 优雅的Hook机制
LinkedHashMap通过重写HashMap的钩子方法实现功能扩展:
// 节点插入后的回调
void afterNodeInsertion(boolean evict) {LinkedHashMap.Entry<K,V> first;if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;removeNode(hash(key), key, null, false, true); // 自动淘汰}
}// 节点访问后的回调
void afterNodeAccess(Node<K,V> e) {// 在访问顺序模式下,将访问的节点移动到链表尾部if (accessOrder && (last = tail) != e) {// 复杂的链表重新链接逻辑}
}
设计精髓:通过Template Method模式,在不修改HashMap核心逻辑的前提下,优雅地扩展了功能。
2. 高效的链表操作
节点链接操作
private void linkNodeAtEnd(LinkedHashMap.Entry<K,V> p) {if (putMode == PUT_FIRST) {// 插入到头部LinkedHashMap.Entry<K,V> first = head;head = p;if (first == null) tail = p;else {p.after = first;first.before = p;}} else {// 插入到尾部LinkedHashMap.Entry<K,V> last = tail;tail = p;if (last == null) head = p;else {p.before = last;last.after = p;}}
}
亮点:
- 统一的链接逻辑处理头部/尾部插入
- 边界条件处理完善(空链表情况)
- O(1)时间复杂度
3. LRU缓存的天然支持
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false; // 默认不删除,子类可重写实现LRU
}
使用示例:
LinkedHashMap<String, String> lruCache = new LinkedHashMap<String, String>(16, 0.75f, true) {protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {return size() > MAX_ENTRIES; // 超过阈值就删除最老的}
};
4. SequencedMap接口的实现(Java 21)
LinkedHashMap实现了新的SequencedMap
接口,提供了:
public V putFirst(K k, V v) {try {putMode = PUT_FIRST;return this.put(k, v);} finally {putMode = PUT_NORM; // 确保模式重置}
}public SequencedMap<K, V> reversed() {return new ReversedLinkedHashMapView<>(this);
}
设计巧思:
- 通过临时设置
putMode
实现定位插入 - 使用try-finally确保状态恢复
- 反向视图通过包装器模式实现,避免数据复制
5. 迭代器的优化设计
abstract class LinkedHashIterator {LinkedHashMap.Entry<K,V> next;boolean reversed;LinkedHashIterator(boolean reversed) {this.reversed = reversed;next = reversed ? tail : head; // 根据方向选择起点}final LinkedHashMap.Entry<K,V> nextNode() {// ...next = reversed ? e.before : e.after; // 根据方向移动return e;}
}
性能优势:
- 迭代时间复杂度O(n),与容量无关
- 支持正向/反向迭代
- 相比HashMap的O(capacity)有显著优势
6. 内存布局的巧妙设计
每个Entry节点同时维护:
- 哈希表关系:
next
指针(链表)或红黑树关系 - 插入顺序关系:
before/after
指针(双向链表)
这种设计让一个节点同时参与两种数据结构,内存效率高且逻辑清晰。