【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;}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【odoo17】后端py方法触发右上角提示组件

概要 在前面文章中&#xff0c;有介绍过前端触发的通知服务。 【odoo】右上角的提示&#xff08;通知服务&#xff09; 此文章则介绍后端触发方法。 内容 直接上代码&#xff1a;但是前提一定是按钮触发&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; def bu…

自动化测试 pytest 中 scope 限制 fixture使用范围!

导读 fixture 是 pytest 中一个非常重要的模块&#xff0c;可以让代码更加简洁。 fixture 的 autouse 为 True 可以自动化加载 fixture。 如果不想每条用例执行前都运行初始化方法(可能多个fixture)怎么办&#xff1f;可不可以只运行一次初始化方法&#xff1f; 答&#xf…

17.延迟队列

介绍 延迟队列&#xff0c;队列内部是有序的&#xff0c;延迟队列中的元素是希望在指定时间到了以后或之前取出和处理。 死信队列中&#xff0c;消息TTL过期的情况其实就是延迟队列。 使用场景 1.订单在十分钟内未支付则自动取消。 2.新创建的店铺&#xff0c;如果十天内没…

【Ant Design Vue的更新日志】

🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步! 以下是Ant Design Vue的更新日志 版本1.7.0(发布日期:2023年4月) …

TCP/IP协议——使用Socket套接字实现

目录 Socket 使用Socket实现TCP客户端和服务器的过程 使用Socket搭建TCP服务器 线程优化 向客户端发送消息 连接的断开 客户端主动断开 服务端主动断开 服务器完整的程序 使用Socket编写客户端程序连接TCP服务器 Socket Socket是一种网络通信协议&#xff0c;它允许…

渗透测试:筑牢网络安全的坚固防线

在当今这个互联网高度发达的时代&#xff0c;网络安全已成为维护社会稳定和经济发展的重要基石。随着互联网的普及&#xff0c;网络攻击手段日益复杂多变&#xff0c;各类安全威胁层出不穷。为了有效应对这些挑战&#xff0c;渗透测试作为一种重要的安全测试与评估方法&#xf…

arduino程序-数字输出-学用led(led电路及相关函数)(基础知识)

arduino程序-数字输出-学用led&#xff08;led电路及相关函数&#xff09;&#xff08;基础知识&#xff09; 1-10 数字输出1-学用ledLED发光二极管LED电压特性电阻 1-11 数字输出arduino控制LEDLed与arduino连接电路图高电平及低电平含义 1-10 数字输出1-学用led 元器件初步介…

关于 AGGLIGATOR(猛禽)网络宽频聚合器

AGGLIGATOR 是一个用于多个链路UDP/IP带宽聚合的工具软件&#xff0c;类似MTCP的作用&#xff0c;不过它是针对UDP/IP宽频聚合的。 举个例子&#xff1a; 中国大陆有三台公网服务器&#xff0c;中国香港有一台大带宽服务器。 那么&#xff1a; AGGLIGATOR 允许中国大陆的客户…

Day7-指针专题二

1. 字符指针与字符串 C语言通过使用字符数组来处理字符串 通常&#xff0c;我们把char数据类型的指针变量称为字符指针变量。字符指针变量与字符数组有着密切关系&#xff0c;它也被用来处理字符串 初始化字符指针是把内存中字符串的首地址赋予指针&#xff0c;并不是把该字符串…

独占电脑资源来执行一个应用

1. 背景 在人工智能时代&#xff0c;随着神经网络的发展&#xff0c;训练人工智能模型需要越来越多的硬件资源&#xff0c;例如&#xff0c;利用10万条棋局数据、使用一台PC电脑、完整地训练一次确定性神经网络五子棋模型&#xff0c;需要花费一年半的时间。随着训练数据的增长…

<PLC><HMI><汇川>在汇川HMI画面中,如何为UI设置全局样式?

前言 汇川的HMI软件是使用了Qt来编写的,因此在汇川的HMI程序编写过程,是支持使用qt的样式来自定义部件样式的,即qss格式。 概述 汇川的软件本身提供三个系统的style样式,我们可以直接使用,但是,如果系统提供的样式不符合你的需求,那么你可以对其进行修改,或者自己新建…

进程间通信与线程间通信的方法汇总

目录 一、进程间通信机制 管道(pipe)&#xff1a; 命名管道(FIFO)&#xff1a; 消息队列(MQ)&#xff1a; 信号量(semaphore)&#xff1a; 共享内存(shared memory)&#xff1a; 信号(signal)&#xff1a; 内存映射(mapped memory)&#xff1a; 内存映射和共享内存的区…

NFTScan 正式上线 ERC404 浏览器和 NFT API 数据服务

近日&#xff0c;NFTScan 团队正式对外发布了 ERC404 浏览器&#xff0c;将为 ERC404 生态的 NFT 开发者和用户提供简洁高效的 NFT 数据搜索查询服务。NFTScan 作为全球领先的 NFT 数据基础设施服务商&#xff0c;帮助用户更方便地访问和分析 ERC404 相关的 NFT 数据&#xff0…

git使用总结

概述 简介 Git是一种代码托管技术&#xff0c;很多代码托管平台也是基于Git来实现的。 Git可以帮我们做到很多的事情&#xff0c;比如代码的版本控制&#xff0c;分支管理等。 网址 git官网&#xff1a;https://git-scm.com/ 版本控制系统【VCS】 可以完整保存项目的快照&#…

力扣Hot100-543二叉树的直径

给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,4,5] 输出&a…

【Vue】权限控制

权限管理 分类&#xff1a; 页面权限功能(按钮)权限接口权限 vue3-element-admin 的实现方案 一般我们在业务中将 路由可以分为两种&#xff0c;constantRoutes 和 asyncRoutes。 constantRoutes&#xff1a; 代表那些不需要动态判断权限的路由&#xff0c;如登录页、404(或…

Skywalking 入门与实战

一 什么是 Skywalking? Skywalking 时一个开源的分布式追踪系统&#xff0c;用于检测、诊断和优化分布式系统的功能。它可以帮助开发者和运维人员深入了解分布式系统中各个组件之间的调用关系、性能瓶颈以及异常情况&#xff0c;从而提供系统级的性能优化和故障排查。 1.1 为…

嵌入式初学-C语言-八

#接嵌入式初学-C语言-七# 分支结构 分支结构&#xff1a;又被称之为选择结构 选择结构的形式 多分支 语法&#xff1a; if(条件1) { 语句1; } else if(条件2) { 语句2; } ... else { 语句n1; }案例&#xff1a; #include <stdio.h> int main() { // 需求&#xff…

Apache、nginx

一、Web 1、概述 Web&#xff1a;为⽤户提供的⼀种在互联⽹上浏览信息的服务&#xff0c;Web 服务是动态的、可交互的、跨平台的和图形化的。 Web 服务为⽤户提供各种互联⽹服务&#xff0c;这些服务包括信息浏览服务&#xff0c;以及各种交互式服务&#xff0c;包括聊天、购物…