Java集合中的LinkedHashMap类

jdk1.8.0_144

  本文阅读最好先了解HashMap底层,可前往《Java集合中的HashMap类》

  LinkedHashMap由于它的插入有序特性,也是一种比较常用的Map集合。它继承了HashMap,很多方法都直接复用了父类HashMap的方法。本文将探讨LinkedHashMap的内部实现,以及它是如何保证插入元素是按插入顺序排序的。

  在分析前可以先思考下,既然是按照插入顺序,并且以Linked-开头,就很有可能是链表实现。如果纯粹以链表实现,也不是不可以,LinkedHashMap内部维护一个链表,插入一个元素则把它封装成Entry节点,并把它插入到链表尾部。功能可以实现,但这带来的查找效率达到了O(n),显然远远大于HashMap在没有冲突的情况下O(1)的时间复杂度。这就丝毫不能体现出Map这种数据结构随机存取快的优点。

  所以显然,LinkedHashMap不可能只有一个链表来维护Entry节点,它极有可能维护了两种数据结构:散列表+链表。

  为便于理解,将不会分析每个方法,会从插入开始分析LinkedHashMap的数据结构及实现。

  LinkedHashMap继承了HashMap类,并且没有重写put方法,而是直接沿用了HashMap#put方法。有关HashMap#put已经在《Java集合中的HashMap类》有了较为详细的介绍。从调用HashMap#put方法可知,它的插入过程和HashMap相同,也就是说它也一样有着和HashMap相同的散列表结构。不过要小心尽管调用的是HashMap#put方法,但在这个方法中有一个方法是构造一个新节点newNode,这里LinkedHashMap重写了,所以调用的是LinkedHashMap#newNode,也正是这个方法实现了对LinkedHashMap链表的维护。

  忽略其余代码,关键代码在HashMap#putVal中tab[i] = newNode(hash, key, value, null),稍后再来查看LinkedHashMap#newNode方法。

  其过程先用图例来说明。

  链表插入过程如下代码所示:

1 //LinkedHashMap#newNode,构造一个新的节点
2 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
3     LinkedHashMap.Entry<K,V> p =
4         new LinkedHashMap.Entry<K,V>(hash, key, value, e);
5     linkNodeLast(p);
6     return p;
7 }  
复制代码
 1 //LinkedHashMap#linkNodeLast,插入到链表尾部
 2 private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
 3     LinkedHashMap.Entry<K,V> last = tail;        //LinkedHashMap定义了tail尾指针和head头指针,且链表为双向链表
 4     tail = p;
 5     if (last == null)
 6         head = p;
 7     else {
 8 //双向链表的插入
 9         p.before = last;
10         last.after = p;
11     }
12 }
复制代码

  对于LinkedHashMap插入,散列表部分和HashMap一致,而双向链表部分则是来一个就插到尾部,这样就保证了保持插入顺序。

  通过插入基本了解了LinkedHashMap的内部实现,get方法很简单,同样是计算出key的hash和对应散列表的下标即可。

  在LinkedHashMap还需要提到三个方法,这三个方法在HashMap中定义,但是并没有具体实现,具体实现放到了LinkedHashMap中。

void afterNodeAccess(Node<K,V> p)

  此方法可以实现通过访问顺序排序,方法中如果定义accessOrder=true,则会将访问(get)过的元素放到链表尾部。accessOrder设置可以通过构造方法传递。

void afterNodeInsertion(boolean evict)

  这个方法在LinkedHashMap并无意义,因为它调用的removeEldestEntry始终返回false,此时程序就会返回不会执行。但如果重写了removeEldestEntry方法,则可以实现LRU(最近最少使用)缓存。

void afterNodeRemoval(Node<K,V> p)

  移除Map中的元素时调用,更新双向链表。

 

 

这是一个能给程序员加buff的公众号 

posted @   OKevin  阅读(9122)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示