JCSprout项目解析:深入理解LinkedHashMap实现原理

JCSprout项目解析:深入理解LinkedHashMap实现原理

JCSprout 👨‍🎓 Java Core Sprout : basic, concurrent, algorithm JCSprout 项目地址: https://gitcode.com/gh_mirrors/jc/JCSprout

前言

在Java集合框架中,LinkedHashMap是一个容易被忽视但非常重要的类。作为HashMap的有序版本,它在很多场景下都能发挥独特作用。本文将从实现原理、数据结构到使用场景,全面剖析LinkedHashMap的内部机制。

LinkedHashMap概述

LinkedHashMap继承自HashMap,在HashMap的基础上维护了一个双向链表来记录元素的插入顺序或访问顺序。这使得它能够提供两种排序方式:

  1. 插入顺序(默认):按照元素被放入Map的顺序进行排序
  2. 访问顺序:每次访问元素后,该元素会被移动到链表末尾

这种特性使得LinkedHashMap非常适合实现LRU(最近最少使用)缓存。

核心数据结构

LinkedHashMap的核心是在HashMap的Entry基础上扩展了双向链表指针:

private static class Entry<K,V> extends HashMap.Entry<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
        super(hash, key, value, next);
    }
}

这种设计巧妙地将哈希表和双向链表结合在一起:

  • 哈希表提供O(1)时间复杂度的查找性能
  • 双向链表维护元素的顺序关系

关键实现细节

1. 初始化过程

LinkedHashMap的初始化会调用父类HashMap的构造方法,并重写了init()方法:

@Override
void init() {
    header = new Entry<>(-1, null, null, null);
    header.before = header.after = header;
}

这里创建了一个环形双向链表,header节点既作为头节点也作为尾节点。

2. 插入元素过程

LinkedHashMap重写了HashMap的以下方法来实现有序插入:

  • addEntry():添加新条目
  • createEntry():创建新条目
  • recordAccess():记录访问

当插入新元素时,除了像HashMap一样放入哈希桶中,还会通过addBefore()方法将元素添加到双向链表尾部:

private void addBefore(Entry<K,V> existingEntry) {
    after = existingEntry;
    before = existingEntry.before;
    before.after = this;
    after.before = this;
}

3. 访问元素过程

当accessOrder为true时,每次get操作都会触发recordAccess()方法,将访问的元素移动到链表末尾:

void recordAccess(HashMap<K,V> m) {
    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
    if (lm.accessOrder) {
        lm.modCount++;
        remove();  // 从当前位置移除
        addBefore(lm.header);  // 添加到链表末尾
    }
}

使用场景分析

1. 保持插入顺序

默认情况下,LinkedHashMap会按照元素插入的顺序进行迭代:

Map<String, String> map = new LinkedHashMap<>();
map.put("a", "1");
map.put("b", "2");
map.put("c", "3");
// 迭代顺序保证是a->b->c

2. 实现LRU缓存

通过设置accessOrder为true,可以轻松实现LRU缓存:

Map<String, String> cache = new LinkedHashMap<>(16, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_CACHE_SIZE;
    }
};

当缓存达到最大容量时,最久未被访问的元素会被自动移除。

性能考虑

LinkedHashMap在HashMap的基础上增加了维护双向链表的开销:

  • 空间开销:每个Entry多了两个指针
  • 时间开销:插入和访问时需要额外维护链表

但由于链表操作都是O(1)的,整体性能仍然很好。

总结

LinkedHashMap通过巧妙结合哈希表和双向链表,在保持HashMap高效查找的同时,提供了有序访问的能力。理解其实现原理有助于我们在实际开发中更好地利用这一特性,特别是在需要有序访问或实现缓存策略的场景下。

JCSprout 👨‍🎓 Java Core Sprout : basic, concurrent, algorithm JCSprout 项目地址: https://gitcode.com/gh_mirrors/jc/JCSprout

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

花淑云Nell

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

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

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

打赏作者

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

抵扣说明:

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

余额充值