前言
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操作的基础上更新链表。 - 提供灵活的顺序模式(插入顺序或访问顺序)。