一句话总结
LinkedHashMap = HashMap + 双向链表,Entry 继承 Node 增加 before/after 指针。默认按插入顺序遍历,设置 accessOrder=true 按访问顺序(最近访问移到末尾)。重写 removeEldestEntry() 即可实现 LRU 缓存(超过容量自动删除最老元素),是面试高频手写题。
初级理解
LinkedHashMap 继承自 HashMap,在 HashMap 的基础上增加了双向链表来维护元素的插入顺序或访问顺序。
核心特点:拥有 HashMap 的所有特性(O(1) 查询),同时可以按插入顺序或访问顺序遍历。
// 默认按插入顺序
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("c", 3);
map.put("a", 1);
map.put("b", 2);
// 遍历顺序 = 插入顺序
map.forEach((k, v) -> System.out.print(k)); // c a b
与 HashMap 的区别:HashMap 遍历顺序不可预测;LinkedHashMap 遍历顺序可预测(插入顺序或访问顺序)。
中级深入
底层数据结构:LinkedHashMap 的 Entry 继承 HashMap.Node,额外增加了 before 和 after 两个指针,形成双向链表。
// LinkedHashMap.Entry 结构
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 双向链表指针
}
两种排序模式:
1. 插入顺序(默认):accessOrder = false,遍历顺序 = 插入顺序
2. 访问顺序:accessOrder = true,最近访问的元素移到链表末尾,遍历顺序 = 访问顺序(从旧到新)
// 访问顺序模式
LinkedHashMap<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
map.get("a"); // 访问 a,a 移到末尾
map.forEach((k, v) -> System.out.print(k)); // b c a
高级拓展
LRU 缓存实现:利用 LinkedHashMap 的访问顺序 + removeEldestEntry 方法,可以轻松实现 LRU(Least Recently Used)缓存。
// 最简单的 LRU 缓存实现
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true); // accessOrder = true
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity; // 超过容量自动删除最老的元素
}
}
LRUCache<String, String> cache = new LRUCache<>(3);
cache.put("a", "1");
cache.put("b", "2");
cache.put("c", "3");
cache.get("a"); // 访问 a
cache.put("d", "4"); // 超过容量,删除最老的 b
// 此时缓存:c, a, d
LinkedHashMap 在 Spring 中的应用:Spring 的 LinkedCaseInsensitiveMap(不区分大小写的 Map)就是继承 LinkedHashMap 实现的。
面试加分项:能手写 LRU 缓存实现(基于 LinkedHashMap),是高频面试题。
实战场景
场景:LRU 缓存完整实现(线程安全版)
// 线程安全的 LRU 缓存
public class ConcurrentLRUCache<K, V> {
private final int capacity;
private final LinkedHashMap<K, V> map;
public ConcurrentLRUCache(int capacity) {
this.capacity = capacity;
this.map = new LinkedHashMap<K, V>(capacity, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
};
}
public synchronized V get(K key) {
return map.get(key);
}
public synchronized void put(K key, V value) {
map.put(key, value);
}
public synchronized int size() {
return map.size();
}
}
面试模拟
Q:LinkedHashMap 和 HashMap 有什么区别?
A:HashMap 遍历顺序不可预测;LinkedHashMap 通过双向链表维护顺序,默认按插入顺序,也可按访问顺序。代价是每个 Entry 多了两个指针(before/after),内存稍高,但操作复杂度不变(O(1))。
Q:如何用 LinkedHashMap 实现 LRU 缓存?
A:1) 构造时设置 accessOrder=true(访问顺序);2) 重写 removeEldestEntry(),当 size() > capacity 时返回 true,自动删除最老的条目。LinkedHashMap 的 afterNodeInsertion() 在每次 put 后调用 removeEldestEntry() 检查。