java-LinkedHashMap源码解析

前言:

LinkedHashMap是HashMap的一个子类,所以它应该继承了HashMap的大部分特性,比如基于哈希表的实现和键值对的存储方式。但LinkedHashMap还有一个额外的特性,就是维护插入顺序或者访问顺序。这意味着在遍历LinkedHashMap时,元素的顺序是可以预测的,要么是插入的顺序,要么是最近访问的顺序(LRU)。这一点和HashMap不同,因为HashMap不保证任何顺序。


原理分析:

核心结构与设计思想

  • 继承关系LinkedHashMap 继承自 HashMap,因此具备哈希表的快速查询能力。

  • 双向链表:在 HashMap 的 Node 结构基础上,添加 before 和 after 指针,形成双向链表,维护键值对的 插入顺序 或 访问顺序(LRU)。

  • 关键参数

    • accessOrder(默认 false):true 表示按访问顺序排序,false 表示按插入顺序排序。

    • head 和 tail:指向双向链表的头部和尾部节点。

Entry结构

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);
    }
}

        扩展 HashMap.Node,新增 before 和 after 指针,构成双向链表。

PUT 流程(源码逐行解析)

// LinkedHashMap 未重写 put(),复用 HashMap 的 putVal()
public V put(K key, V value) {
    return super.put(key, value);
}

// 重写 newNode() 创建带双向链表指针的 Entry
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<>(hash, key, value, e);
    linkNodeLast(p); // 将新节点链接到链表末尾
    return p;
}

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}

 插入新节点

  1. 调用 HashMap 的 putVal() 完成哈希表插入。

  2. 通过 newNode() 创建 Entry 节点,并通过 linkNodeLast() 将其添加到双向链表尾部。

Get流程

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder) // 若为访问顺序模式,调整节点位置
        afterNodeAccess(e);
    return e.value;
}

// 将被访问节点移动到链表末尾
void afterNodeAccess(Node<K,V> e) {
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e;
        LinkedHashMap.Entry<K,V> b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

访问顺序调整

  • 若 accessOrder=true,调用 afterNodeAccess() 将被访问节点移动到链表末尾。

删除节点(源码解析)

// 重写 removeNode() 后的回调
void afterNodeRemoval(Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e;
    LinkedHashMap.Entry<K,V> b = p.before, a = p.after;
    p.before = p.after = null; // 解除节点链接
    if (b == null)
        head = a;
    else
        b.after = a;
    if (a == null)
        tail = b;
    else
        a.before = b;
}
  • 删除节点时:从双向链表中解除该节点的前后链接。

LRU缓存淘汰策略

// 插入新节点后的回调(可用于淘汰最旧节点)
void afterNodeInsertion(boolean evict) {
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

// 默认返回 false,需重写以实现淘汰逻辑
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

 淘汰最旧节点

  • 若 removeEldestEntry() 返回 true,则删除链表头部节点(最久未访问)。


流程图解析:

PUT 操作流程

graph TD
    A[调用 HashMap.putVal()] --> B[创建 LinkedHashMap.Entry 节点]
    B --> C[linkNodeLast: 添加到双向链表尾部]
    C --> D[触发 afterNodeInsertion 回调]
    D --> E{是否开启淘汰策略?}
    E -- 是 --> F[检查 removeEldestEntry 条件]
    F -- true --> G[删除链表头部节点]
    E -- 否 --> H[结束]

GET 操作流程(accessOrder=true)

graph TD
    A[调用 HashMap.getNode()] --> B[找到对应节点]
    B --> C{accessOrder 是否为 true?}
    C -- 是 --> D[调用 afterNodeAccess 调整节点到链表尾部]
    C -- 否 --> E[直接返回 value]

 迭代顺序实现

  • 遍历方式:直接遍历双向链表,而非哈希表。

abstract class LinkedHashIterator {
    LinkedHashMap.Entry<K,V> next = head; // 从 head 开始遍历
    LinkedHashMap.Entry<K,V> current = null;
    // ...
}

总结:

特性LinkedHashMapHashMap
数据结构哈希表+双向链表哈希表+链表/红黑树
顺序保证插入顺序或访问顺序无顺序保证
迭代性能O(n),直接遍历链表纯键值对存储,无需顺序
使用场景需要顺序访问吗,LRU缓存纯兼职存储,无需顺序
  • 核心价值:通过双向链表维护顺序,以少量空间代价换取可预测的迭代顺序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在下陈平安

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值