当前位置: 首页 > news >正文

Java的LinkedHashMap 源码解析

LinkedHashMap 是 Java 中的一种有序 Map,它扩展了 HashMap,提供了有序的元素存储方式。在 LinkedHashMap 中,元素的有序性可以按照插入顺序或访问顺序来维护,而这个有序性是通过维护一个双向链表来实现的,这也是实现 LRU Cache 功能的基础。在接下来的源码解析中,我们将深入探讨 LinkedHashMap 的实现。

在这里插入图片描述

主要结论

在开始分析源码之前,让我们首先总结一些关键结论:

  1. LinkedHashMap 继承了 HashMap,因此它的底层数据结构与 HashMap 相同,都是由数组、链表或红黑树组成,同时也使用相同的扩容机制。

    /*** 用指定的初始容量、加载因子和排序模式构造一个空的LinkedHashMap实例。** @param initialCapacity 初始容量* @param loadFactor      加载因子* @param accessOrder     排序模式 - <tt>true</tt> 表示按访问顺序,<tt>false</tt> 表示按插入顺序* @throws IllegalArgumentException 如果初始容量为负数或加载因子为非正数*/
    public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {// 调用 HashMap 的构造方法,继承了 HashMap 的底层数据结构和扩容机制super(initialCapacity, loadFactor);this.accessOrder = accessOrder;
    }
    
  2. LinkedHashMap 使用双向链表来维护数据的顺序,与 HashMap 的拉链式存储方式不同。

    /** 实现说明。此类的先前版本在内部结构上略有不同。由于超类 HashMap 现在对一些节点使用树结构,类 LinkedHashMap.Entry 现在被视为中介节点类,也可以转换为树形结构。在当前上下文中,此类的名称 LinkedHashMap.Entry 在几个方面都有点令人困惑,但不能更改。否则,即使它不被导出到此包外,已知一些现有源代码依赖于调用 removeEldestEntry 时的符号解析特例规则,以抑制由于模糊的用法而导致的编译错误。因此,我们保留名称以保持编译性不变。** 节点类的更改还需要使用两个字段(head、tail)而不是指向头节点的指针来维护双向链接的前后列表。该类以前也使用了不同风格的回调方法来进行访问、插入和删除。*//*** 用于常规 LinkedHashMap 条目的 HashMap.Node 子类。*/
    static class Entry<K,V> extends HashMap.Node<K,V> {Entry<K,V> before, after;Entry(int hash, K key, value, Node<K,V> next) {super(hash, key, value, next);}
    }
    
  3. LinkedHashMap 存储顺序与添加顺序一致,但也可以通过 accessOrder 参数决定是否在访问元素时移动元素,以实现 LRU 缓存功能。

    /*** 在删除元素之后,将元素从双向链表中删除*/
    void afterNodeRemoval(Node<K,V> e) { // unlinkLinkedHashMap.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;elseb.after = a;if (a == null)tail = b;elsea.before = b;
    }/*** 在访问元素之后,将该元素放到双向链表的尾巴处*/
    void afterNodeAccess(Node<K,V> e) { // move node to lastLinkedHashMap.Entry<K,V> last;if (accessOrder && (last = tail) != e) {LinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.after = null;if (b == null)head = a;elseb.after = a;if (a != null)a.before = b;elselast = b;if (last == null)head = p;else {p.before = last;last.after = p;}tail = p;++modCount;}
    }
    

内部结构

LinkedHashMap 内部类 Entry 继承自 HashMap 的 Node,同时增加了 “前继节点” 和 “后继节点” 来支持双向链表的特性。此外,LinkedHashMap 中还有两个重要的成员变量:

  • head:记录 LinkedHashMap 的头节点。
  • tail:记录 LinkedHashMap 的尾节点。
  • accessOrder:表示是否根据访问顺序进行排序,如果 accessOrder 为 true,LinkedHashMap 会在元素被访问时将其移至链表末尾,实现 LRU 缓存。

内部方法

LinkedHashMap 定义了一些私有的内部方法,用于操作双向链表:

  • linkNodeLast(LinkedHashMap.Entry<K,V> p):将元素连接到链表尾部。

    /*** 将元素连接到链表尾部。** @param 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;}
    }
    
  • transferLinks(LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst):将一个节点的前继节点和后继节点链接到另一个节点。

    /*** 将一个节点的前继节点和后继节点链接到另一个节点。** @param src 要转移链接的源节点* @param dst 目标节点*/
    private void transferLinks(LinkedHashMap.Entry<K,V> src,LinkedHashMap.Entry<K,V> dst) {LinkedHashMap.Entry<K,V> b = dst.before = src.before;LinkedHashMap.Entry<K,V> a = dst.after = src.after;if (b == null)head = dst;elseb.after = dst;if (a == null)tail = dst;elsea.before = dst;
    }
    
  • afterNodeRemoval(Node<K,V> e):在删除元素后,将元素从双向链表中删除。

    /*** 在删除元素后,将元素从双向链表中删除。** @param e 要删除的元素*/
    void afterNodeRemoval(Node<K,V> e) { 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;elseb.after = a;if (a == null)tail = b;elsea.before = b;
    }
    
  • afterNodeInsertion(boolean evict):在插入元素后,可能删除最老的元素。

    /*** 在插入元素后,可能删除最老的元素。** @param evict 如果为 true,可能会删除最老的元素*/
    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);}
    }
    
  • afterNodeAccess(Node<K,V> e):在访问元素后,将元素放到链表的尾部。

    /*** 在访问元素后,将元素放到链表的尾部。** @param e 要移动的元素*/
    void afterNodeAccess(Node<K,V> e) { // 将节点移动到最后LinkedHashMap.Entry<K,V> last;if (accessOrder && (last = tail) != e) { // 如果 accessOrder 与 (last = tail) != e 为 trueLinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.after = null;if (b == null)head = a;elseb.after = a;if (a != null)a.before = b;elselast = b;if (last == null)head = p;else {p.before = last;last.after = p;}tail = p;++modCount;}
    }
    

构造方法

LinkedHashMap 提供了多个构造方法,包括:

  • LinkedHashMap(int initialCapacity, float loadFactor):指定初始容量和加载因子的构造方法。

    /*** 指定初始容量和加载因子的构造方法。** @param initialCapacity 初始容量* @param loadFactor 加载因子*/
    public LinkedHashMap(int initialCapacity, float loadFactor) {super(initialCapacity, loadFactor);accessOrder = false;
    }
    
  • LinkedHashMap(int initialCapacity):指定初始容量的构造方法。

    /*** 指定初始容量的构造方法。** @param initialCapacity 初始容量*/
    public LinkedHashMap(int initialCapacity) {super(initialCapacity);accessOrder = false;
    }
    
  • LinkedHashMap():默认构造方法。

    /*** 默认构造方法。*/
    public LinkedHashMap() {super();accessOrder = false;
    }
    
  • LinkedHashMap(Map<? extends K, ? extends V> m):使用现有映射初始化构造方法。

    /*** 使用现有映射初始化构造方法。** @param m 要使用的映射*/
    public LinkedHashMap(Map<? extends K, ? extends V> m) {super();accessOrder = false;putMapEntries(m, false);
    }
    
  • LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder):可以设置 accessOrder 参数以决定是否按照访问顺序排序。

    /*** 可以设置 accessOrder 参数以决定是否按照访问顺序排序。** @param initialCapacity 初始容量* @param loadFactor 加载因子* @param accessOrder 如果为 true,则按照访问顺序排序;否则按照插入顺序排序*/
    public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;
    }
    

LinkedHashMap 的源码解析包括实现 newNodereplacementNodenewTreeNodereplacementTreeNode 等节点插入和替换的方法,以及实现 containsValuegetgetOrDefaultkeySetentrySet 等其他操作。这些方法负责在 LinkedHashMap 中维护元素的顺序和实现 LRU 缓存功能。

总结

LinkedHashMap 是一个有序的 Map,它维护元素的有序性,可以按照插入顺序或访问顺序排列元素。这个有序性是通过维护一个双向链表来实现的。另外,LinkedHashMap 提供了 accessOrder 参数来决定是否在访问元素时移动元素,以实现 LRU 缓存功能。这使得 LinkedHashMap 成为一个非常有用的数据结构,适用于需要有序性和缓存功能的场景。

相关文章:

Java的LinkedHashMap 源码解析

LinkedHashMap 是 Java 中的一种有序 Map&#xff0c;它扩展了 HashMap&#xff0c;提供了有序的元素存储方式。在 LinkedHashMap 中&#xff0c;元素的有序性可以按照插入顺序或访问顺序来维护&#xff0c;而这个有序性是通过维护一个双向链表来实现的&#xff0c;这也是实现 …...

Linux系统及常用指令

目录 1、什么是Linux系统 2、为什么要用Linux系统 3、Linux系统的种类 4、如何安装Linux系统 5、常见的适配器种类 6、学习第一个Linux指令 7、安装ssh客户端软件 8、Linux系统的目录结构 9、Linux的常用命令 9.1 目录切换命令 9.2 查看目录下的内容 9.3 查看当前…...

Mac Electron 应用如何进行签名(signature)和公证(notarization)?

最近很多客户反映&#xff0c;从官网下载的Mac Electron应用打不开&#xff0c;直接报病毒&#xff0c;类似于这种&#xff1a; 这是因为在MacOS 10.14.5之后&#xff0c;如果应用没有在苹果官方平台进行公证notarization(我们可以理解为安装包需要审核&#xff0c;来判断是否存…...

【C++ | 抽象类】纯虚函数 和 抽象基类,为什么需要抽象基类

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…...

DP(7) | 打家劫舍① | Java | LeetCode 198, 213, 337 做题总结(未完)

打家劫舍问题 来源于代码随想录&#xff1a;https://programmercarl.com/0198.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.html#%E6%80%9D%E8%B7%AF ① 确定dp数组&#xff08;dp table&#xff09;以及下标的含义 dp[i]&#xff1a;考虑下标i&#xff08;包括i&#xff09;以内的房…...

人工智能算法工程师(中级)课程17-模型的量化与部署之剪枝技巧与代码详解

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能算法工程师(中级)课程17-模型的量化与部署之剪枝技巧与代码详解。模型剪枝是深度学习领域中一项关键的技术&#xff0c;旨在减少神经网络中的冗余权重&#xff0c;从而降低计算成本和内存占用&#xff0c;同…...

JavaScript 实例:掌握编程技巧

JavaScript 实例:掌握编程技巧 JavaScript 是一种广泛使用的编程语言,它为网页添加交互性,是现代网络开发的重要组成部分。本文将通过一系列实例,帮助您更好地理解和掌握 JavaScript 的核心概念和编程技巧。 基础实例:变量和数据类型 首先,让我们从最基础的开始。Java…...

自己做小项目时,配置的Maven需要用阿里云私服加速Jar包的下载

在我的IDEA中&#xff0c;maven配置在了这个地址&#xff0c;然后我需要去这个地址下找到settings.xml的maven配置文件来配置以下的阿里云私服地址来加速jar包的下载&#xff01;【不然就是下N年很慢&#xff01;】...

Linux笔记之time命令测量命令的执行时间

Linux笔记之time命令测量命令的执行时间 在Linux中&#xff0c;time命令用于测量命令的执行时间。这对于分析和优化脚本或程序的性能非常有用。time命令会显示三个主要时间指标&#xff1a; real: 从命令开始到结束的实际时间&#xff08;也称为挂钟时间&#xff09;。user: …...

《基于 CDC、Spark Streaming、Kafka 实现患者指标采集》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…...

重要的单元测试

&#x1f47d;System.out.println(“&#x1f44b;&#x1f3fc;嗨&#xff0c;大家好&#xff0c;我是代码不会敲的小符&#xff0c;目前工作于上海某电商服务公司…”); &#x1f4da;System.out.println(“&#x1f388;如果文章中有错误的地方&#xff0c;恳请大家指正&…...

什么是diff算法?

Diff算法&#xff0c;全称为Difference算法&#xff0c;是一种用于比较和查找两个对象&#xff08;如文本、源代码、数据结构或任何形式的字符串&#xff09;之间差异的算法。它在多个领域有着广泛的应用&#xff0c;包括但不限于前端开发、版本控制系统、协同编辑工具等。以下…...

BUUCTF逆向wp [MRCTF2020]Transform

第一步 查壳。该题为64位。 第二步 进入主函数&#xff0c;跟进dword_40F040,它应该与关键字符串有关 分析一下&#xff1a; 初始化和输入 sub_402230(argc, argv, envp); 这行可能是一个初始化函数&#xff0c;用于设置程序环境或处理命令行参数。具体功能不明&#xff0c…...

前端下载文件流 出现乱码 解决方案

1. 后端返回文件格式不是 utf-8 解决方案&#xff1a;后端加 2. 若添加 utf-8 后依旧乱码 请求配置中添加 responseType: arraybuffer, export function downMode() {return http.request({url: baseUrl downTemplate,method: get,responseType: arraybuffer,}); }下载 con…...

Linux/Windows 系统分区

1. Windows 系统 1.1 系统分区 系统分区也叫做磁盘分区&#xff0c;即分盘&#xff1b; 举个例子&#xff0c;好比家里有一个大柜子&#xff0c;把衣服&#xff0c;鞋子&#xff0c;袜子都放在里面&#xff0c;由于没有隔断&#xff0c;找的时候非常麻烦&#xff0c;找是能找…...

C/C++ xml库

文章目录 一、介绍1.1 xml 介绍1.2 xml 标准1.3 xml 教程1.4 xml 构成 二、C/C xml 库选型2.1 选型范围2.2 RapidXML2.3 tinyxml22.4 pugixml2.5 libxml 五、性能比较5.1 C xml 相关的操作有哪些5.2 rapidxml、Pugixml、TinyXML2 文件读取性能比较 六、其他问题6.1 version和 e…...

UniVue@v1.5.0版本发布:里程碑版本

前言 以后使用UniVue都推荐使用1.5.0以后的版本&#xff0c;这个版本之后&#xff0c;更新的速度将会放缓。 希望这个框架能够切实的帮助大家更好的开发游戏&#xff0c;做出一款好游戏&#xff01;本开源项目采用的开源协议为MIT协议&#xff0c;完全开源化&#xff0c;以后也…...

在 Windows 上开发.NET MAUI 应用_2.生成你的第一个应用

先决条件 Visual Studio 2022 17.8 或更高版本&#xff0c;并安装了 .NET Multi-platform App UI 工作负载。 可参考上一篇文章&#xff1a;http://t.csdnimg.cn/n38Yy 创建应用 1.启动 Visual Studio 2022。 在开始窗口中&#xff0c;单击“创建新项目”以创建新项目&#…...

配置SMTP服务器的要点是什么?有哪些限制?

配置SMTP服务器安全性如何保障&#xff1f;如何高效配置服务器&#xff1f; SMTP作为电子邮件发送的核心协议&#xff0c;其配置对于确保邮件的成功传递和安全至关重要。AokSend将详细介绍配置SMTP服务器的关键要点&#xff0c;帮助读者建立一个高效、安全的邮件发送系统。 配…...

图形渲染基础-Unity渲染管线介绍

Unity中的渲染管线渲染场景主要分为三个阶段 剔除&#xff08;Culling&#xff09; 剔除摄像机不可见对象&#xff08;视锥体剔除Frustum Culling&#xff09;和被遮挡对象&#xff08;遮挡剔除Occlusion Culling&#xff09;。 渲染&#xff08;Rendering&#xff09; 将可见…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

ip子接口配置及删除

配置永久生效的子接口&#xff0c;2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

React---day11

14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store&#xff1a; 我们在使用异步的时候理应是要使用中间件的&#xff0c;但是configureStore 已经自动集成了 redux-thunk&#xff0c;注意action里面要返回函数 import { configureS…...

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列&#xff1f;2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...