LinkedHashMap 是如何保证返回的顺序性的?

LinkedHashMap 源码阅读

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

先来看一下 LinkedHashMap 的继承关系,它继承了 HashMap,并且实现了 Map 接口。

LinkedHashMap 底层是 数组 + 链表 的形式,它通过双向的链表将插入进去的每个节点链接起来,以达到顺序返回的目的。

本篇内容主要关注 LinkedHashMap 是如何实现和维护这种链表结构的,以及 LinkedHashMap 的遍历是如何实现的。


在这里插入图片描述

上图展示的是 LinkedHashMap 中维护的大致结构,每个节点之间通过链相连,构成一个连续的双向链表,其中的节点是 LinkedHashMap 的静态内部类:

/**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    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,分别指向它的后一个节点和前一个节点。

接下来具体来看一下,LinkedHashMap 中是如何维护链表结构的:

    public static void main(String[] args) {
        LinkedHashMap<Object, Object> hashMap = new LinkedHashMap<>();
        hashMap.put(1, 1);
        System.out.println(hashMap);
    }

上面展示的是一个简单的主方法,通过调试这段代码来阅读其 put() 方法的源码。

V put(K key, V value)

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

调用的仍然是 HashMap 的 put 方法,这个方法一定不陌生了,具体请看这篇文章:
万字源码解析!彻底搞懂 HashMap【二】:putVal 方法和 resize 方法(重点)

如果仅仅是调用了这个方法肯定是无法形成上面的双向链表的,那唯一的可能就是 LinkedHashMap 中重写了某些被 putVal() 调用的 HashMap 中的方法,通过调试可以很清楚的知道,重写的是 newNode() 方法

在这里插入图片描述

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e)

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }

方法中实例化了一个 LinkedHashMap.Entry 对象,然后调用了 linkNodeLast() 方法,就是通过这个方法将链表节点连接起来的

void linkNodeLast(LinkedHashMap.Entry<K,V> p)

// link at the end of list
    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;
        }
    }

首先获取了此时的 last 节点,也就是链表的最后一个元素,然后做了一个判断,如果 last 是空的,就说明此时数组中没有元素,是第一次添加,此时就将 head 也置为 p 节点,否则就将 p 与 last 节点双向连接。

读到这里其实就可以大致了解到 LinkedHashMap 的机制,就是将 HashMap 中的 Node 节点替换为了类中的 Entry,新增了 before 和 after 节点来创造出一个依据顺序连接的双向链表,由此来达到存储插入顺序的目的。

再来看一下删除的方法

V remove(Object key)

public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

同样,也是调用了父类 HashMap 中的 removeNode() 方法,然后重写其中调用的某个方法来达到维护链表的顺序。

HashMap 中的这个方法的实现也比较简单,这里就不赘述了,就是通过哈希映射到数组的某个索引,然后寻找需要删除的节点,下面展示的是其找到节点后执行删除逻辑的代码:

if (node != null && (!matchValue || (v = node.value) == value ||
                     (value != null && value.equals(v)))) {
    if (node instanceof TreeNode)
        ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
    else if (node == p) // 是第一个节点
        tab[index] = node.next;
    else // 是中间的某个节点
        p.next = node.next;
    ++modCount;
    --size;
    afterNodeRemoval(node);
    return node;
}

在 LinkedHashMap 中重写了 afterNodeRemoval() 方法去维护链表的顺序性,传入的 node 在本次 remove() 中被删除的节点,注意它实际上是 LinkedHashMap.Entry 实例,它还有指向链表前后元素的指针,所以并不会被清除。

void afterNodeRemoval(Node<K,V> e)

void afterNodeRemoval(Node<K,V> e) { // unlink
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, 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;
    }

首先使用 p 将传入的节点保存下来(向下转型变为 LinkedHashMap.Entry<K,V>),使用 a 和 b 保存链表中节点的前后节点,然后就是对链表的维护,方式也比较简单,就是将前一个节点和后一个节点连接。

然后来看一下 LinkedHashMap 是如何遍历的,Map 集合的遍历是依赖于其中的 entrySet,可以通过 entrySet() 来获取这个 Set 集合,方法返回的是一个 Set<Map.Entry<K, V>> 类型的集合,其中 K 是键的类型,V 是值的类型。

通过 Set 集合可以利用迭代器或者增强 for 循环实现遍历,其中使用的迭代器为 LinkedEntryIterator

LinkedEntryIterator

    final class LinkedEntryIterator extends LinkedHashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }

这个类在 LinkedHashIterator 的基础上实现了 Iterator 接口,LinkedHashIterator 是一个公用的继承类,很多迭代器都通过继承它来实现一些方法

在这里插入图片描述

能保证返回顺序的关键是其中的 nextNode() 方法:

LinkedHashMap.Entry<K,V> nextNode()

        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            next = e.after;
            return e;
        }

可以看到此时的 next 指向的是 e.after 即使用的是链表中维护的顺序,这样就能保证返回的顺序性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/535335.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Eland上传bge-base-zh-v1.5向量化模型到ElasticSearch中

最近需要做一些向量检索&#xff0c;试试ES 一、准备 系统&#xff1a;MacOS 14.3.1 ElasticSearch&#xff1a;8.13.2 Kibana&#xff1a;8.13.2 本地单机环境&#xff0c;无集群&#xff0c;也不基于Docker BGE是一个常见的文本转向量的模型&#xff0c;在很多大模型RAG应…

RK3588平台开发系列讲解(GMAC delay开发篇)

目录 RGMII Delayline 获取步骤 代码确认 节点确认 扫描 delayline 窗口 测试扫描出来的中间值 自动扫描 硬件 RGMII Delayline 获取步骤 如果你的项目具有千兆以太网功能&#xff0c;使用的是 RGMII 接口&#xff0c;只要有硬件差别&#xff0c;都需要重新做一次 delay…

今天讲讲MYSQL数据库事务怎么实现的!

目录 什么是数据库事务 Mysql如何保证原子性 Mysql如何保证持久性 MySQL怎么保证隔离性 事务隔离级别 脏读的解决 不可重复读的解决 幻读的解决 MVCC实现 Read View 那么RC、RR级别下的InnoDB快照读有什么不同&#xff1f; 什么是数据库事务 数据库事务是指一组数据…

鸿蒙让我赚到了第一笔桶金!年薪33.6W!

抢人&#xff01;抢人&#xff01;抢人&#xff01; 所谓抢滩鸿蒙&#xff0c;人才先行。鸿蒙系统火力全开后&#xff0c;抢人已成鸿蒙市场的主题词&#xff01; 智联招聘数据显示&#xff0c;春节后首周&#xff0c;鸿蒙相关职位数同比增长163%&#xff0c;是去年同期的2.6倍…

【包编译】库文件安装错位置怎么办

背景&#xff1a; 在建图的工作空间mapping中&#xff0c;编译好了GeographphicLib-2.3之后&#xff0c;对工作空间mapping进行编译&#xff0c;报错&#xff0c;找不到下面这俩。 总结&#xff1a; 原因&#xff1a;因为GeographphicLib的库文件在编译的时候没有放到默认系统…

“人工智能+数字人”,让数字技术赋能多领域智能化管理、数字化服务

AI数字人结合了语音合成、语音识别、语义理解、图像处理、虚拟形象驱动等多项AI核心技术&#xff0c;可以实现导览服务、信息播报、互动交流、业务咨询等智能化功能。 如今&#xff0c;AI数字人逐渐被政务、文旅、展馆展厅、博物馆、数字会议、金融、校园等等领域多元化应用&am…

springboot如何切换内置web服务器?

切换内置web服务器 这是没有引入web依赖的服务 这是引入web依赖的服务 由此可知默认是tomcat服务器 那么如何切换内置服务器 只要有对应服务器的坐标即可自动切换&#xff0c;先排除tomcat再引入依赖&#xff0c;比如切换成jetty服务器 <dependency><groupId>org…

SQL Serve---查询

概要 1、order by子句 —默认asc&#xff08;升序&#xff09;、desc&#xff08;降序&#xff09; 2、distinct关键字 3、group by子句 4、聚合函数 —max()、min()、sum()、avg()、count() 5、having子句 6、compute子句 英文关键字 order by 排序 asc…

【SpringBoot整合系列】SpringBoot整合FastDFS(二)

目录 SpringBoot整合FastDFSJava客户端/依赖常用api接口解释1.uploadFile参数返回值 2.uploadSlaveFile参数返回值 3.getMetadata参数返回值 4.overwriteMetadata参数&#xff1a;返回值&#xff1a;无 5.mergeMetadata参数&#xff1a;返回值&#xff1a;无 6.queryFileInfo参…

linux重定向符号

将ls命令执行结果重定向到a文件中 将错误ls命令执行结果重定向到a文件中&#xff08;这里用到前面的标准错误输出重定向&#xff09;

python linux服务器ssh简单爆破(测试用户名密码)(连接ssh服务器)(测试登录ssh服务器)

文章目录 背景示例代码代码解释导入模块SSH服务器的地址和端口用户名和密码列表生成所有可能的用户名和密码组合尝试连接到SSH服务器并验证用户名和密码遍历并测试每一对凭证 背景 我们华为摄像头linux终端的密码忘了&#xff0c;还不太好初始化&#xff0c;手动一个个测试太麻…

宏观认知第一篇--AI 是否就是第四次工业革命?

今年春节期间李一舟老师突然爆火&#xff0c;成功晋升为能与 ChatGPT 公司 CEO 齐名的中国 AI 大佬&#xff0c;赚到几个小目标后又火速被封&#xff0c;于是想着有空写篇小文章讲一讲跟普通人切身相关的话题-- AI 是否就是第四次工业革命&#xff1f; “AI 是否就是第四次工业…

数学杂谈之三:数学思想方法

数学杂谈之三&#xff1a;数学思想方法 数学杂谈之一&#xff1a;数学的形态 https://blog.csdn.net/cnds123/article/details/137437208 数学杂谈之二&#xff1a;数学中的概念和理解 https://blog.csdn.net/cnds123/article/details/137500537 数学思维、数学思想和数学方法…

SpringBoot学习(一)引入、分析、核心

文章目录 SpringBoot特性示例总结简化整合简化开发简化配置简化部署简化运维 Spring Initializer创建向导 应用分析依赖管理机制自动配置机制初步理解完整流程 SpringBoot学习点 核心技能常用注解YAML配置文件基本语法示例辅助工具lombok 日志配置简介格式组成记录日志日志级别…

看AI赋能数智化 | Gooxi AI服务器闪耀CITE 2024

4月9日“中国电子信息博览会暨2024 AI算力产业大会”在深圳如期开展&#xff0c;Gooxi携最新产品、行业应用全栈解决方案出席盛会&#xff0c;全面展示Gooxi回应数智新时代下机遇与挑战的丰富AI创新实践成果。 All in AI&#xff0c;奔赴新质生产力 作为中国领先的服务器解决…

题目 2348: 信息学奥赛一本通T1436-数列分段II【二分答案】

信息学奥赛一本通T1436-数列分段II - C语言网 (dotcpp.com) #include<iostream> #include<algorithm> #include<cstring> using namespace std; #define int long long const int N1e5100; const int inf1e9; int n,m; int a[N]; bool check(int mid) {int s…

Linux网络名称空间和虚拟机有何区别

在Linux系统中&#xff0c;网络名称空间和虚拟机都是实现资源隔离和虚拟化的技术&#xff0c;但它们在设计理念、实现机制、资源消耗、使用场景等方面存在着显著的区别。本文旨在全方位、系统性地分析这两种技术的区别。&#x1f50d; 1. 设计理念与实现机制 1.1. 网络名称空…

中通科技数仓数据治理实践

目录 一、背景 1.1 中通数仓架构介绍 1.2 中通数仓层级划分 1.3 中通数据现状 1.4 中通数仓现面临的压力 二、数据仓库具体实践 2.1 时效治理 2.1.1 数据入仓治理 2.1.2 核心模型治理 2.2 存储治理 2.3 内存治理 2.3.1 内存浪费治理 2.3.2 数据倾斜治理 2.3.3 内…

10:00面试,10:08就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

基于 MATLAB 和 App Designer 的 UI 交互框架开发的一款电力系统潮流计算工具

基于 MATLAB 和 App Designer 的 UI 交互框架开发的一款电力系统潮流计算工具 文章目录 基于 MATLAB 和 App Designer 的 UI 交互框架开发的一款电力系统潮流计算工具一、软件介绍二、软件功能1、数据输入 2、潮流作业设置3、 潮流结果报表及可视化三、 软件设计思路1 、牛顿拉…