洞察 LinkedHashMap 的奥秘:按序排列元素的容器
2024-02-21 12:52:33
一、LinkedHashMap的存储机制
LinkedHashMap 和 HashMap 一样,底层都采用哈希表来存储键值对,即利用键的哈希码进行快速查找。然而,为了实现有序存储,LinkedHashMap 在哈希表的基础上引入了双向链表。
1. 核心数据结构:哈希表和链表
LinkedHashMap 使用哈希表存储键值对,并通过链表将哈希表中的元素连接起来,形成一个有序的键值对序列。
2. 哈希表的职责:快速查找
哈希表是 LinkedHashMap 的骨架,负责根据键的哈希码快速定位元素。在 LinkedHashMap 中,哈希表的每个桶都对应一个链表,链表中的节点存储着键值对。
3. 链表的职责:有序存储
链表是 LinkedHashMap 实现有序存储的关键。通过链表,LinkedHashMap 可以将哈希表中的键值对按照插入顺序连接起来,从而实现对元素的有序遍历和访问。
二、LinkedHashMap的实现原理
1. 构造函数
LinkedHashMap 的构造函数接收两个参数:initialCapacity 和 loadFactor。
initialCapacity 指定了哈希表的初始容量,即哈希表中桶的数量。loadFactor 指定了哈希表的装填因子,即哈希表中元素数量与桶数量之比。
2. put 方法
当向 LinkedHashMap 中添加一个键值对时,put 方法首先计算键的哈希码,然后根据哈希码确定要将键值对插入哪个桶。
如果桶中还没有元素,则直接将键值对插入桶中。
如果桶中已有元素,则将键值对插入链表的末尾。
3. get 方法
当从 LinkedHashMap 中获取一个键值对时,get 方法首先计算键的哈希码,然后根据哈希码确定要从哪个桶中查找键值对。
如果桶中没有该键值对,则返回 null。
如果桶中有多个该键值对,则返回链表中第一个找到的键值对。
4. remove 方法
当从 LinkedHashMap 中删除一个键值对时,remove 方法首先计算键的哈希码,然后根据哈希码确定要从哪个桶中删除键值对。
如果桶中没有该键值对,则返回 null。
如果桶中有多个该键值对,则删除链表中第一个找到的键值对。
三、LinkedHashMap的应用场景
LinkedHashMap 在各种场景中都有着广泛的应用,例如:
1. 缓存
在缓存系统中,经常需要按最近最少使用 (LRU) 的原则来管理缓存中的数据。LinkedHashMap 可以很好地满足这一需求,因为 LinkedHashMap 可以通过迭代链表来实现对数据的 LRU 管理。
2. 队列
在队列中,元素需要按照先进先出 (FIFO) 的原则进行处理。LinkedHashMap 可以通过迭代链表来实现对队列的 FIFO 管理。
3. 日志记录
在日志记录系统中,经常需要按时间顺序记录日志。LinkedHashMap 可以通过迭代链表来实现对日志的按时间顺序记录。
四、LinkedHashMap的源码分析
LinkedHashMap 的源码位于 java.util 包中,其源代码相对复杂,涉及到很多细节。我们这里仅对 LinkedHashMap 中一些关键的方法进行源码分析。
1. put 方法
public V put(K key, V value) {
// 计算键的哈希码
int hash = hash(key.hashCode());
// 根据哈希码确定要将键值对插入哪个桶
int i = indexFor(hash, table.length);
// 如果桶中还没有元素,则直接将键值对插入桶中
if (table[i] == null) {
root = new Entry<K, V>(hash, key, value, null);
table[i] = root;
first = root;
last = root;
size = 1;
return null;
}
// 如果桶中已有元素,则将键值对插入链表的末尾
for (Entry<K, V> e = table[i]; e != null; e = e.next) {
// 如果找到了相同的键,则更新值并返回旧值
if (e.hash == hash && e.key.equals(key)) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 如果没有找到相同的键,则将键值对插入链表的末尾
last.next = new Entry<K, V>(hash, key, value, last);
last = last.next;
size++;
return null;
}
2. get 方法
public V get(Object key) {
// 计算键的哈希码
int hash = hash(key.hashCode());
// 根据哈希码确定要从哪个桶中查找键值对
int i = indexFor(hash, table.length);
// 如果桶中没有元素,则返回 null
if (table[i] == null) {
return null;
}
// 如果桶中有多个该键值对,则返回链表中第一个找到的键值对
for (Entry<K, V> e = table[i]; e != null; e = e.next) {
if (e.hash == hash && e.key.equals(key)) {
e.recordAccess(this);
return e.value;
}
}
// 如果桶中没有找到该键值对,则返回 null
return null;
}
3. remove 方法
public V remove(Object key) {
// 计算键的哈希码
int hash = hash(key.hashCode());
// 根据哈希码确定要从哪个桶中删除键值对
int i = indexFor(hash, table.length);
// 如果桶中没有元素,则返回 null
if (table[i] == null) {
return null;
}
// 如果桶中有多个该键值对,则删除链表中第一个找到的键值对
Entry<K, V> prev = null;
for (Entry<K, V> e = table[i]; e != null; e = e.next) {
if (e.hash == hash && e.key.equals(key)) {
// 如果找到了相同的键,则删除该键值对
if (prev == null) {
table[i] = e.next;
} else {
prev.next = e.next;
}
size--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
prev = e;
}
// 如果桶中没有找到该键值对,则返回 null
return null;
}
五、结语
LinkedHashMap 作为 Java 中一种特殊的有序映射,以其有序存储的特性在各类场景中发挥着重要作用。通过本文对 LinkedHashMap 的深入解析,我们不仅了解了其内部结构和实现原理,还掌握了其用法和应用场景。相信您能够将 LinkedHashMap 运用到自己的项目中,提升开发效率和代码质量。