前言
LinkedHashMap
是 Java
集合框架中的一种数据结构,它继承自 HashMap
,并通过维护一个双向链表来保证元素的插入顺序或访问顺序。下面我会从其定义、特性、源码实现以及使用场景等方面展开讲解。
LinkedHashMap
是 HashMap
的子类,位于 java.util
包中。它的主要特点是:
- 有序性:默认按照插入顺序(insertion-order)维护键值对,也可以通过配置支持访问顺序(access-order)。
- 底层结构:结合了哈希表(HashMap)和双向链表,兼具快速查找($O(1)$ 时间复杂度)和顺序遍历的能力。
- 线程不安全:与
HashMap
一样,LinkedHashMap
不是线程安全的,多线程环境下需要外部同步。
源码分析
LinkedHashMap
的类定义如下:
1 | public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> |
数据结构
LinkedHashMap
在 HashMap
的基础上增加了一个双向链表,用于维护顺序。它的核心节点类是 Entry
,定义如下:
1 | static class Entry<K,V> extends HashMap.Node<K,V> { |
HashMap.Node
是哈希表的节点,包含hash
、key
、value
和next
(用于处理哈希冲突)。before
和after
是LinkedHashMap
特有的,用于连接前后节点,形成双向链表。
此外,LinkedHashMap
还维护了链表的头尾指针:
1 | transient LinkedHashMap.Entry<K,V> head; // 链表头部 |
构造方法
LinkedHashMap
提供了多个构造方法,其中一个关键参数是 accessOrder
:
1 | public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { |
accessOrder = false
(默认):按插入顺序。accessOrder = true
:按访问顺序(每次get
或put
会调整链表)。
核心方法
LinkedHashMap
的大部分操作(如 put
、get
)直接复用了 HashMap
的实现,但通过钩子方法(Hook Method)扩展了链表维护逻辑。
put
方法
LinkedHashMap
本身没有重写put
,而是依赖HashMap
的put
。- 在
HashMap
中,put
会调用newNode
创建新节点,而LinkedHashMap
重写了这个方法:
1 | Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { |
linkNodeLast
方法:
1 | private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { |
get
方法
- 默认情况下,
LinkedHashMap
的get
直接调用HashMap
的实现。 - 当
accessOrder = true
时,get
会触发元素移动到链表尾部:
1 | public V get(Object key) { |
afterNodeAccess
方法:
1 | void afterNodeAccess(Node<K,V> e) { |
remove
方法
删除时需要同时维护哈希表和链表,LinkedHashMap
重写了 afterNodeRemoval
:
1 | void afterNodeRemoval(Node<K,V> e) { |
LRU 缓存支持
LinkedHashMap
提供了一个方法 removeEldestEntry
,用于实现 LRU(Least Recently Used)缓存:
1 | protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { |
通过重写这个方法,可以在插入新元素时移除最老的元素。例如:
1 | LinkedHashMap<Integer, String> lruMap = new LinkedHashMap<>(16, 0.75f, true) { |
后记
核心特性
- 插入顺序:默认情况下,迭代
LinkedHashMap
时,元素按放入的顺序返回。 - 访问顺序:通过构造参数
accessOrder
设置为true
,可以让LinkedHashMap
按访问顺序排列(最近访问的元素移到链表末尾,适用于 LRU 缓存场景)。 - 性能:查找、插入、删除操作的时间复杂度与
HashMap
一致(平均 $O(1)$),但维护链表会带来少量额外开销。
使用场景
- 需要顺序的映射:如按插入顺序输出键值对。
- LRU 缓存实现:通过设置
accessOrder = true
并重写removeEldestEntry
,实现简单的缓存。 - 日志记录:记录事件并按时间顺序遍历。
总结
从源码角度看,LinkedHashMap
通过继承 HashMap
并添加双向链表,巧妙地实现了有序性。它的核心在于:
- 通过
Entry
类扩展节点,维护前后指针。 - 通过钩子方法(如
newNode
、afterNodeAccess
)在HashMap
操作的基础上更新链表。 - 提供灵活的顺序模式(插入顺序或访问顺序)。