1. 概述
本文将深入探讨 LinkedHashMap
类的内部实现机制。作为 Map
接口最常用的实现之一,LinkedHashMap
继承自 HashMap
,并共享其核心构建块。建议先熟悉 HashMap 实现原理 再继续阅读。
2. LinkedHashMap vs HashMap
LinkedHashMap
在大多数方面与 HashMap
高度相似,但通过结合哈希表和双向链表增强了功能:
- ✅ 底层结构:默认容量为 16 的数组 + 贯穿所有条目的双向链表
- ✅ 核心改造:扩展了
HashMap.Node
类,添加前后指针维持顺序
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);
}
}
⚠️ 关键点:链表定义了迭代顺序,默认为插入顺序(insertion-order)
3. 插入顺序模式
当创建默认 LinkedHashMap
时,元素会严格按插入顺序排列,且在整个生命周期内保持该顺序:
@Test
public void givenLinkedHashMap_whenGetsOrderedKeyset_thenCorrect() {
LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
map.put(1, null);
map.put(2, null);
map.put(3, null);
map.put(4, null);
map.put(5, null);
Set<Integer> keys = map.keySet();
Integer[] arr = keys.toArray(new Integer[0]);
for (int i = 0; i < arr.length; i++) {
assertEquals(new Integer(i + 1), arr[i]);
}
}
✅ 测试保证:插入顺序始终维持(HashMap
无法保证此特性)
❌ 注意:键的重新插入不会影响已有顺序
4. 访问顺序模式
通过特殊构造函数可启用访问顺序(access-order)模式:
LinkedHashMap<Integer, String> map = new LinkedHashMap<>(16, .75f, true);
参数说明:
- 初始容量(16)
- 负载因子(0.75)
- 排序模式(true=访问顺序)
🔥 核心特性:迭代顺序变为"最近最少使用"(LRU)顺序,每次 put/get
都会更新访问顺序:
@Test
public void givenLinkedHashMap_whenAccessOrderWorks_thenCorrect() {
LinkedHashMap<Integer, String> map
= new LinkedHashMap<>(16, .75f, true);
map.put(1, null);
map.put(2, null);
map.put(3, null);
map.put(4, null);
map.put(5, null);
Set<Integer> keys = map.keySet();
assertEquals("[1, 2, 3, 4, 5]", keys.toString());
map.get(4); // 访问键4
assertEquals("[1, 2, 3, 5, 4]", keys.toString());
map.get(1); // 访问键1
assertEquals("[2, 3, 5, 4, 1]", keys.toString());
map.get(3); // 访问键3
assertEquals("[2, 5, 4, 1, 3]", keys.toString());
}
⚠️ 重要:只有显式访问操作(如 get/put
)会影响顺序,迭代操作不会
4.1 实现 LRU 缓存
通过重写 removeEldestEntry()
可自动淘汰最旧条目:
public class MyLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private static final int MAX_ENTRIES = 5;
public MyLinkedHashMap(
int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
}
测试效果(容量上限 5):
@Test
public void givenLinkedHashMap_whenRemovesEldestEntry_thenCorrect() {
LinkedHashMap<Integer, String> map
= new MyLinkedHashMap<>(16, .75f, true);
map.put(1, null);
map.put(2, null);
map.put(3, null);
map.put(4, null);
map.put(5, null);
Set<Integer> keys = map.keySet();
assertEquals("[1, 2, 3, 4, 5]", keys.toString());
map.put(6, null); // 淘汰键1
assertEquals("[2, 3, 4, 5, 6]", keys.toString());
map.put(7, null); // 淘汰键2
assertEquals("[3, 4, 5, 6, 7]", keys.toString());
map.put(8, null); // 淘汰键3
assertEquals("[4, 5, 6, 7, 8]", keys.toString());
}
5. 性能考量
操作 | 时间复杂度 | 与 HashMap 对比 |
---|---|---|
基本操作 | O(1) | 略慢于 HashMap(链表维护开销) |
迭代操作 | O(n) | 优于 HashMap(n=条目数) |
关键差异:
- ✅
LinkedHashMap
迭代时间仅与条目数相关 - ❌
HashMap
迭代时间 = 容量 + 条目数(O(size+capacity)) - ⚠️ 初始容量过高对
LinkedHashMap
的影响小于HashMap
6. 并发处理
与 HashMap
相同,LinkedHashMap
非线程安全。多线程访问时需外部同步:
Map m = Collections.synchronizedMap(new LinkedHashMap());
⚠️ 特殊注意:在访问顺序模式下,get()
操作也属于结构性修改(与 put/remove
同级)
7. 总结
LinkedHashMap
通过维护双向链表实现了:
- ✅ 插入顺序保证
- ✅ 访问顺序支持(LRU 缓存基础)
- ✅ 迭代性能优化
选择建议:
- 需要保留插入顺序 → 直接使用
- 实现 LRU 缓存 → 访问顺序 + 重写
removeEldestEntry()
- 高并发场景 → 外部同步或改用
ConcurrentHashMap
完整示例代码见 GitHub 项目