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

【HashMap源码学习】

HashMap的底层结构

HashMap是基于分离链表法解决散列冲突的动态散列表。

1、在jdk7中,使用的是“数组 + 链表”,发生散列冲突的时候键值对会用头插法添加到单链表中;

2、在jdk8中,使用的是“数组 + 链表 + 红黑树”,发生散列冲突的时候会使用尾插法添加到单链表中。如果链表的长度大于8且散列表容量大于64的时候,会将链表树化为红黑树。在扩容再散列时,如果红黑树的长度低于6则会还原为链表。

     1)HashMap的数组长度保证是2的整数次幂,且默认数组容量是16,默认装载因子是0.75,扩容阈值是12,树化阈值是8(当一个桶中的元素个数大于等于8时进行树化),还原阈值是6(当一个桶中的元素个数小于等于6时把树转化为链表)。2)hashmap的 key 和 value 都支持null,key为null的键值对会直接映射到数组下表为0的桶中(因为null的hash值是0,取余映射到数组下标后也是table[0]的桶)。3)首次加入数据如果数组为空,则扩容,默认数组容量是16;数组容量到达阈值(12-24...)会扩容至原来容量2倍;链表长度大于8且数组容量小于64时,同样会扩容至原来容量2倍,数组容量到64时则会树化。 

源码分析

1、putVal 存放数据

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {HashMap.Node<K, V>[] tab;HashMap.Node<K, V> p;int n, i;if ((tab = table) == null || (n = tab.length) == 0)// 如果数组为null或长度为0,则进行扩容:首次扩容数组长度为16,阈值为12n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)// 如果数组 tab[i]位置上是null,则将加入的 key-value 放入此数组节点tab[i] = newNode(hash, key, value, null);else {// 数组 tab[i]位置上有值,即 pHashMap.Node<K, V> e;K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))// tab[i]上元素的hash值和key值 和 新加入元素的hash值 key值都相同; 则 p保存到e中用于后续修改value值e = p;else if (p instanceof TreeNode) // 红黑树节点情况e = ((HashMap.TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);else {  // 单链表情况,尾插for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) { // p的下一节点是空的,则尾插新元素p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1)// 链表上大于8个元素了,树化(数组长度大于64后,才开始树化,否则仅扩容16-32-64)treeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))// 新加入的元素 和 单链表上元素 hash、key值都相同break;p = e; // 前有e = p.next,即 p指向p的下一个节点}}if (e != null) { // e不为空,则新加入的元素 hash、key 都重复了,则新值替换旧值V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;// 访问节点回调(用于 LinkedHashMap,默认为空实现)afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)// hashmap的元素个数大于阈值(12-24....)时,则进行扩容, 扩容后长度是原来长度的两倍resize();afterNodeInsertion(evict);return null;}

2、扩容

final Node<K,V>[] resize() {// 定义 旧数组 变量Node<K,V>[] oldTab = table;// 如果数组为 null 则旧容量置为0// 如数组不为 null 则旧容量为置为 数组的长度(划重点:数组的长度)int oldCap = (oldTab == null) ? 0 : oldTab.length;// 定义 旧扩容阈值 变量int oldThr = threshold;// 定义 新容量 新扩容阈值 变量为 0int newCap, newThr = 0;// 1、第一步// 如果旧容量 > 0(表示不是第一次添加元素,数组里面有元素)if (oldCap > 0) {// 极端情况:// 如果旧容量 >= 最大容量,则此时无法扩容,将扩容阈值设置为整数最大值,直接返回旧容量// static final int MAXIMUM_CAPACITY = 1 << 30;if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}// 普通情况:// oldCap << 1 旧容量扩容为原来的两倍// (新容量 < 最大容量) 且 (旧容量 >= 默认容量)else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)// 扩容阈值扩大为原来的两倍newThr = oldThr << 1; // double threshold}// 使用非无参构造方法创建的map,第一次插入元素会走到这里else if (oldThr > 0)// 初始化容量 置为 扩容阈值newCap = oldThr;// 调用无参构造方法创建的map,第一次插入元素会走到这里else {// static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16// 新容量 默认为 16newCap = DEFAULT_INITIAL_CAPACITY;// 新扩容阈值 = 默认装载因子 * 默认初始化容量newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}// 带初始容量和负载因子的构造方法走这里if (newThr == 0) {// 使用容量乘以负载因子计算扩容阈值float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}// 最终计算的扩容阈值threshold = newThr;// 2、第二步// 使用新容量 创建新数组Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;// 如果旧数组不等于 null,则将旧数组上的键值对 再散列到新数组上if (oldTab != null) {// 遍历旧数组上的每个桶for (int j = 0; j < oldCap; ++j) {Node<K,V> e;// 如果此下标处的桶不为nullif ((e = oldTab[j]) != null) {// 传递给 e 后,置为空oldTab[j] = null;// 如果这个桶中只有一个元素if (e.next == null)// 则计算它在新桶中的位置并把它搬移到新桶中(也就是 直接再散列)newTab[e.hash & (newCap - 1)] = e;// 如果是红黑树else if (e instanceof TreeNode)// 以红黑树的方式再散列((TreeNode<K,V>)e).split(this, newTab, j, oldCap);// 以链表的形式再散列else { // preserve orderNode<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;// 如果元素的哈希值与旧数组长度的按位与运算结果为0,将元素添加到低位链表中。// 如果低位链表为空,将该元素作为链表的头节点,否则将该元素添加到低位链表的尾部。if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}// 如果元素的哈希值与旧数组长度的按位与运算结果不为0,将元素添加到高位链表中。// 如果高位链表为空,将该元素作为链表的头节点,否则将该元素添加到高位链表的尾部。else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;}

相关文章:

【HashMap源码学习】

HashMap的底层结构 HashMap是基于分离链表法解决散列冲突的动态散列表。 1、在jdk7中&#xff0c;使用的是“数组 链表”&#xff0c;发生散列冲突的时候键值对会用头插法添加到单链表中&#xff1b; 2、在jdk8中&#xff0c;使用的是“数组 链表 红黑树”&#xff0c;发…...

Git关联本地仓库和远程仓库

Step 1 添加远程仓库: git remote add <远程仓库别名><远程仓库地址> Step 2 git push -u <远程仓库名><分支名> 查看远程仓库: git remote -v 拉取远程仓库内容: 拉取服务器仓库过程中&#xff0c;如果本地和服务器有文件冲突&#xff0c;则会拉取失…...

【Django】在vscode中新建Django应用并新增路由

文章目录 打开一个终端输入新建app命令在app下的views.py内写一个视图app路由引入该视图项目路由引入app路由项目(settings.py)引入app&#xff08;AntappConfig配置类&#xff09;运行项目 打开一个终端 输入新建app命令 python manage.py startapp antapp在app下的views.py内…...

DT浏览器首页征集收录海内外网址

DT浏览器首页征集收录海内外网址&#xff0c;要求页面整洁&#xff0c;内容丰富&#xff0c;知识性和可读性强&#xff0c;符合大众价值观&#xff0c;不含恶意代码...

便携解码耳放

想象一下&#xff0c;你正在拥挤的地铁上&#xff0c;耳机里传来的音乐却仿佛带你置身于音乐厅&#xff0c;每一个音符都清晰、动人。这不是科幻小说&#xff0c;而是便携解码耳放&#xff08;DAC/AMP&#xff09;带给你的真实体验。无论你是在旅行、通勤还是在咖啡馆里工作&am…...

响应式编程框架Reactor之 Flux 和 Mono 的介绍和区别

Flux和Mono在Reactor框架中都是响应式编程模型的重要概念,它们在处理异步数据流时发挥着重要作用,两者之间也存在一些差异。 Mono的介绍 基本概念: Mono是Reactor中的一个类,它表示一个异步的单个值或零个值的结果。Mono可以看作是一个特殊的Publisher,用于产生数据流,…...

2.3 openCv 对矩阵执行掩码操作

在矩阵上进行掩模操作相当简单。其基本思想是根据一个掩模矩阵(也称为核)来重新计算图像中每个像素的值。这个掩模矩阵包含的值决定了邻近像素(以及当前像素本身)对新的像素值产生多少影响。从数学角度来看,我们使用指定的值来做一个加权平均。 具体而言,掩模操作通常涉…...

贪心算法(三) ---cmp_to_key, 力扣452,力扣179

目录 cmp_to_key 比较函数 键函数 cmp_to_key 的作用 使用 cmp_to_key 代码解释 力扣452 ---射气球 题目 分析 代码 力扣179 ---最大数 题目 分析 代码 cmp_to_key 在Python中&#xff0c;cmp_to_key 是一个函数&#xff0c;它将一个比较函数转换成一个键函数…...

学生信息管理系统详细设计文档

一、设计概述 学生信息管理系统是一个用于管理学生信息的软件系统&#xff0c;旨在提高学校对学生信息的管理效率。本系统主要包括学生信息管理、课程信息管理、成绩信息管理、班级信息管理等功能模块。详细设计阶段的目标是确定各个模块的实现算法&#xff0c;并精确地表达这…...

leetcode10 -- 正则表达式匹配

题目描述&#xff1a; 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 . 和 * 的正则表达式匹配。 . 匹配任意单个字符* 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是要涵盖 整个 字符串 s的&#xff0c;而不是部分字符串。 示例 1&#xff1…...

Binius-based zkVM:为Polygon AggLayer开发、FPGA加速的zkVM

1. 引言 近日&#xff0c;ZK硬件加速巨头Irreducible和Polygon团队宣布联合开发生产级的Binius-based zkVM&#xff0c;用于助力Polygon AggLayer&#xff0c;实现具有低开销、硬件加速的binary proofs。 Irreducible&#xff08;曾用名为Ulvetanna&#xff09;团队 Benjamin …...

基于 HTML+ECharts 实现的大数据可视化平台模板(含源码)

构建大数据可视化平台模板&#xff1a;基于 HTML 和 ECharts 的实现 大数据的可视化对于企业决策、市场分析和业务洞察至关重要。通过直观的数据展示&#xff0c;团队可以快速理解复杂的数据模式&#xff0c;发现潜在的业务机会。本文将详细介绍如何利用 HTML 和 ECharts 实现一…...

特征工程在机器学习中的重要性

特征工程在机器学习中的重要性 特征工程在机器学习中占据着至关重要的地位&#xff0c;它是连接原始数据与机器学习模型之间的桥梁。通过特征工程&#xff0c;我们可以将原始数据转换为机器学习算法能够有效利用的形式&#xff0c;从而提高模型的性能和准确性。以下是特征工程…...

【css】flex布局父元素宽度或高度无法被子元素撑开-bug记录

简言 flex布局父元素宽度或高度无法被子元素撑开问题。 解决方案&#xff1a; 手动计算子元素内容所占宽高&#xff0c;手动赋值给父元素即可。 flex布局宽高度问题 flex布局现在是特别常见得布局方式。 在此记录一个注意点&#xff1a;flex布局在不换行得情况下&#xff0c…...

Music Tag Editor Pro for Mac:强大的音频标签管理工具

Music Tag Editor Pro for Mac是一款专为Mac系统设计的音频标签管理工具&#xff0c;其简易直观的操作界面和强大的功能深受用户喜爱。 这款软件的核心功能在于它能够批量编辑各类音频文件的标签。无论是修改元数据、重命名文件&#xff0c;还是转换音乐标签的文本编码&#x…...

2024秋招算法

文章目录 参考资料一 数组1.1 二分查找1.2 移除元素1.3 长度最小的子数组1.4 螺旋矩阵1.5 在排序数组中查找元素的第一个和最后一个位置 二 链表2.1 移除链表元素2.2 设计链表2.3 反转链表2.4 两两交换链表中的节点2.5 删除链表的倒数第N个节点2.6 链表相交2.7 环形链表II 三 哈…...

El-Table 表格的表头字段切换

最近写了一个小功能&#xff0c;比较有意思&#xff0c;特此博客记录。 提出需求&#xff1a;需要表头字段变化&#xff0c;但是我在官网上的表格相关上查找&#xff0c;没有发现便捷方法。 于是我有两个想法&#xff1a;1.做三个不同的表格。2.做一个表格使用不同的表头字段。…...

分布式事务 详解

1.简介 2.本地事务失效问题 可以使用AOP starter aspectJ 代理 这样就可以拿到它的上下文的代理对象&#xff0c;当然是有这样的需求才这么做 如果你的事务只是想默认的传播行为&#xff0c;共用上面的事务&#xff0c;就可以不用这个啦 详情请去了解 Raft 算法 还有 pa…...

【git】太大了失败: fatal: fetch-pack: invalid index-pack output

#‘’ Git仓库过大致使clone失败的解决方法 上述大神的方法&#xff0c;亲测有效 中途失败: 太大了 fetch-pack: unexpected disconnect while reading sideband packet fatal: early EOF fatal: fetch-pack: invalid index-pack output关闭压缩 git config --global core.…...

在 ArchLinux 上编译运行 axmol 引擎

本文将在 Windows 10 上安装 Arch WSL 中编译 axmol 请确保 WSL2 已正确安装 1. 在微软应用商店安装 ArchLinux 2. 打开 Arch&#xff0c;按照提示输入用户名和密码&#xff0c;尽量简单 3. 配置清华源&#xff0c;速度快的起飞&#xff0c;否则&#xff0c;各种包会安装失败…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

工程地质软件市场:发展现状、趋势与策略建议

一、引言 在工程建设领域&#xff0c;准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具&#xff0c;正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案

在大数据时代&#xff0c;海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构&#xff0c;在处理大规模数据抓取任务时展现出强大的能力。然而&#xff0c;随着业务规模的不断扩大和数据抓取需求的日益复杂&#xff0c;传统…...

Django RBAC项目后端实战 - 03 DRF权限控制实现

项目背景 在上一篇文章中&#xff0c;我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统&#xff0c;为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…...