前言:
LinkedHashMap是HashMap的一个子类,所以它应该继承了HashMap的大部分特性,比如基于哈希表的实现和键值对的存储方式。但LinkedHashMap还有一个额外的特性,就是维护插入顺序或者访问顺序。这意味着在遍历LinkedHashMap时,元素的顺序是可以预测的,要么是插入的顺序,要么是最近访问的顺序(LRU)。这一点和HashMap不同,因为HashMap不保证任何顺序。
原理分析:
核心结构与设计思想
-
继承关系:
LinkedHashMap继承自HashMap,因此具备哈希表的快速查询能力。 -
双向链表:在
HashMap的Node结构基础上,添加before和after指针,形成双向链表,维护键值对的 插入顺序 或 访问顺序(LRU)。 -
关键参数:
-
accessOrder(默认false):true表示按访问顺序排序,false表示按插入顺序排序。 head和tail:指向双向链表的头部和尾部节点。
-
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 指针,构成双向链表。
PUT 流程(源码逐行解析)
// LinkedHashMap 未重写 put(),复用 HashMap 的 putVal()
public V put(K key, V value) {
return super.put(key, value);
}
// 重写 newNode() 创建带双向链表指针的 Entry
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<>(hash, key, value, e);
linkNodeLast(p); // 将新节点链接到链表末尾
return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
插入新节点:
-
调用
HashMap的putVal()完成哈希表插入。 - 通过
newNode()创建Entry节点,并通过linkNodeLast()将其添加到双向链表尾部。
Get流程
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder) // 若为访问顺序模式,调整节点位置
afterNodeAccess(e);
return e.value;
}
// 将被访问节点移动到链表末尾
void afterNodeAccess(Node<K,V> e) {
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e;
LinkedHashMap.Entry<K,V> b = p.before, a = p.after;
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;
}
}
访问顺序调整:
-
若
accessOrder=true,调用afterNodeAccess()将被访问节点移动到链表末尾。
删除节点(源码解析)
// 重写 removeNode() 后的回调
void afterNodeRemoval(Node<K,V> e) {
LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e;
LinkedHashMap.Entry<K,V> b = p.before, a = p.after;
p.before = p.after = null; // 解除节点链接
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
-
删除节点时:从双向链表中解除该节点的前后链接。
LRU缓存淘汰策略
// 插入新节点后的回调(可用于淘汰最旧节点)
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);
}
}
// 默认返回 false,需重写以实现淘汰逻辑
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
淘汰最旧节点:
- 若
removeEldestEntry()返回true,则删除链表头部节点(最久未访问)。
流程图解析:
PUT 操作流程
graph TD
A[调用 HashMap.putVal()] --> B[创建 LinkedHashMap.Entry 节点]
B --> C[linkNodeLast: 添加到双向链表尾部]
C --> D[触发 afterNodeInsertion 回调]
D --> E{是否开启淘汰策略?}
E -- 是 --> F[检查 removeEldestEntry 条件]
F -- true --> G[删除链表头部节点]
E -- 否 --> H[结束]
GET 操作流程(accessOrder=true)
graph TD
A[调用 HashMap.getNode()] --> B[找到对应节点]
B --> C{accessOrder 是否为 true?}
C -- 是 --> D[调用 afterNodeAccess 调整节点到链表尾部]
C -- 否 --> E[直接返回 value]
迭代顺序实现
-
遍历方式:直接遍历双向链表,而非哈希表。
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next = head; // 从 head 开始遍历
LinkedHashMap.Entry<K,V> current = null;
// ...
}
总结:
| 特性 | LinkedHashMap | HashMap |
| 数据结构 | 哈希表+双向链表 | 哈希表+链表/红黑树 |
| 顺序保证 | 插入顺序或访问顺序 | 无顺序保证 |
| 迭代性能 | O(n),直接遍历链表 | 纯键值对存储,无需顺序 |
| 使用场景 | 需要顺序访问吗,LRU缓存 | 纯兼职存储,无需顺序 |
-
核心价值:通过双向链表维护顺序,以少量空间代价换取可预测的迭代顺序。
1579

被折叠的 条评论
为什么被折叠?



