LinkedHashMap实现有序的详细步骤
LinkedHashMap是Java集合框架中的一个重要类,它通过特定的实现方式保证了元素的遍历顺序与插入顺序一致(或访问顺序)。以下是LinkedHashMap实现有序的详细步骤和原理:
1. 数据结构基础
LinkedHashMap继承自HashMap,在HashMap的基础上进行了扩展:
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
2. 维护双向链表
LinkedHashMap在HashMap的数组+链表/红黑树结构基础上,额外维护了一个双向链表:
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);
}
}
3. 实现有序的关键字段
LinkedHashMap类中定义了以下重要字段:
// 双向链表的头节点(最老的节点)
transient LinkedHashMap.Entry<K,V> head;
// 双向链表的尾节点(最新的节点)
transient LinkedHashMap.Entry<K,V> tail;
// 排序模式:true-访问顺序,false-插入顺序
final boolean accessOrder;
4. 插入顺序维护机制
当插入新元素时,LinkedHashMap会执行以下步骤:
- 调用父类HashMap的put方法:完成基本的哈希表插入操作
- 创建LinkedHashMap.Entry节点:而不是普通的HashMap.Node
- 链接到双向链表尾部:
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; } }
5. 访问顺序维护机制(当accessOrder=true时)
当访问一个已存在的元素时(get或put更新),LinkedHashMap会:
- 移动访问的节点到链表尾部:
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, 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; } }
6. 迭代顺序保证
LinkedHashMap的迭代器直接遍历双向链表而非哈希表:
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
LinkedHashIterator() {
next = head; // 从头节点开始
current = null;
}
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
current = e;
next = e.after; // 沿着链表向后遍历
return e;
}
// ... 其他方法
}
7. 删除节点时的链表维护
当删除节点时,LinkedHashMap会同时更新双向链表:
void afterNodeRemoval(Node<K,V> e) {
LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, 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;
}
总结
LinkedHashMap通过以下方式实现有序:
- 继承HashMap的所有功能
- 使用双向链表维护所有节点的顺序
- 在节点插入、访问和删除时维护链表结构
- 迭代时直接遍历双向链表而非哈希表
这种实现方式既保留了HashMap的高效查找特性,又提供了可预测的迭代顺序。
你提供的代码片段是 LinkedHashMap 中的一个关键方法 afterNodeAccess(Node<K,V> e),它负责在访问顺序模式(accessOrder=true)下维护双向链表的顺序。下面我将详细解释这段代码的逻辑和作用。
方法作用
当 accessOrder=true 时(即按访问顺序排序),每次访问(get() 或 put() 更新)某个节点时,会调用 afterNodeAccess(e),将该节点移动到双向链表的尾部(表示它是最近访问的节点)。
为什么移动节点到尾部?
- LRU(Least Recently Used)缓存策略:尾部表示最近访问,头部表示最久未访问。
- 迭代顺序:遍历
LinkedHashMap时,会从头部(最老)到尾部(最新)顺序访问。
代码逐行解析
void afterNodeAccess(Node<K,V> e) { // e 是被访问的节点
LinkedHashMap.Entry<K,V> last; // 用于记录当前尾节点
if (accessOrder && (last = tail) != e) { // 1. 检查是否需要调整顺序
LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e; // 2. 强制转换为Entry(带before/after指针)
LinkedHashMap.Entry<K,V> b = p.before; // 3. 前驱节点
LinkedHashMap.Entry<K,V> a = p.after; // 4. 后继节点
p.after = null; // 5. 断开当前节点的后继指针(准备移动)
// 6. 处理前驱节点(b)
if (b == null)
head = a; // 如果前驱是null,说明e是头节点,新头节点变成a
else
b.after = a; // 否则,前驱节点直接指向后继节点(跳过e)
// 7. 处理后继节点(a)
if (a != null)
a.before = b; // 如果后继存在,它的前驱指向b(跳过e)
else
last = b; // 如果后继是null,说明e是尾节点,last暂存b
// 8. 将e移动到链表尾部
if (last == null)
head = p; // 链表为空(理论上不会发生)
else {
p.before = last; // e的前驱指向原尾节点
last.after = p; // 原尾节点的后继指向e
}
tail = p; // 更新尾节点为e
++modCount; // 修改计数器(用于fail-fast迭代)
}
}
操作流程(图示)
假设双向链表当前状态:
head → A → B → C → tail(accessOrder=true)
-
访问节点 B(调用
afterNodeAccess(B)):- 断开
B的前后连接:A → C - 将
B移动到尾部:A → C → B - 新链表:
head → A → C → B → tail
- 断开
-
再次访问 A:
- 断开
A的连接:head → C → B - 移动
A到尾部:C → B → A - 新链表:
head → C → B → A → tail
- 断开
关键点总结
-
条件检查
- 仅当
accessOrder=true且当前节点不是尾节点时,才需要调整。
- 仅当
-
链表操作
- 将节点
e从原位置移除(处理前驱和后继的指针)。 - 将
e插入到链表尾部(使其成为最新访问的节点)。
- 将节点
-
LRU 应用
- 结合
removeEldestEntry()方法,可实现 LRU 缓存淘汰策略(淘汰头节点)。
- 结合
示例场景
LinkedHashMap<Integer, String> map = new LinkedHashMap<>(16, 0.75f, true);
map.put(1, "A");
map.put(2, "B");
map.put(3, "C");
map.get(2); // 调用afterNodeAccess,链表顺序变为:1 → 3 → 2
map.get(1); // 顺序变为:3 → 2 → 1
通过这种方式,LinkedHashMap 在 accessOrder=true 时,能始终保持最近访问的节点在尾部,从而实现 LRU 行为。
259

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



