【底层学习】HashMap源码学习

成员变量

// 默认初始容量 就是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;// 默认加载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;// 树化阈值(链表转为树的阈值)
static final int TREEIFY_THRESHOLD = 8;// 取消树化阈值(红黑树转为链表)
static final int UNTREEIFY_THRESHOLD = 6;// 链表转红黑树的数组长度的临界值 
static final int MIN_TREEIFY_CAPACITY = 64;// hashmap中的数组结构
transient Node<K,V>[] table;// HashMap中的元素个数
transient int size;// HashMap被修改的次数
transient int modCount;// 要调整大小的下一个大小值,计算公式是(容量 * 加载因子)。
int threshold;// 实际的加载因子
final float loadFactor;

注意:
JDK1.7 及以前:HashMap 底层是数组+链表
JDK1.8 之后:HashMap 底层是数组+链表 或者 数组+红黑树
存储在 HashMap 集合中的元素都将是一个 Map.Entry 的内部接口的实现
什么时候采用数组+链表,什么时候采用数组+红黑树,以及相互转换,取决于链表(红黑树)的长度。当链表长度大于 8 会转为红黑树,当链表长度小于 6,红黑树转为链表。

构造函数

注意:以下部分是基于JDK1.8进行编写的

空参构造

// 使用默认初始容量 (16) 和默认负载系数 (0.75) 构造一个空的 HashMap 。
public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // 其他所有字段都是默认
}

有一个参数的构造函数

构造一个空的 HashMap ,具有指定的初始容量和默认负载系数 (0.75)。

public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

有两个参数的构造函数

构造一个具有指定初始容量和负载因子的空 HashMap

public HashMap(int initialCapacity, float loadFactor) {// 初始化容量小于0,错误if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);// 初始化容量大于定义的最大容量,便把最大的值赋值if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " + loadFactor);this.loadFactor = loadFactor;//调用tableSizeFor方法计算出不小于initialCapacity的最小的2的幂的结果,并赋给成员变量thresholdthis.threshold = tableSizeFor(initialCapacity);
}

tableSizeFor 方法计算源码如下:

static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

有一个Map类型参数的构造方法

public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;// 调用PutMapEntries()来完成HashMap的初始化赋值过程putMapEntries(m, false);
}

putMapEntries 方法调用了HashMap的resize()扩容方法和putVal()存入数据方法

HashMap 中的方法

put方法

流程图:
在这里插入图片描述
思路:

  1. 第一次 put 会先进行扩容
  2. 找到要插入的位置
    2.1. 这个位置没值:直接插入
    2.2. 这个位置有值:
    2.2.1. 判断是否 key 存在,存在的话覆盖 value
    2.2.2. 不存在,判断是否红黑树
    2.2.2.1. 如果是红黑树:调用树化插值方法
    2.2.2.2. 如果是链表:加到链表尾部,判断转树阈值,大于 8 转为红黑树
  3. 判断长度是否满足扩容(长度*扩容因子)?若满足,则动态扩容

在这里插入图片描述

源码如下:代码大部分都标有注释,有些代码需要反复阅读可能会理解的更好

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;// 第一次 put 值的时候,会触发下面的 resize(),类似 java7 的第一次 put 也要初始化数组长度// 第一次 resize 和后续的扩容有些不一样,因为这次是数组从 null 初始化到默认的 16 或自定义的初始容量if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// 找到具体的数组下标,如果这个位置没有值的话,就直接在这个位置初始化node放在这里就好了if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else { // 走到这里说明这个位置有值Node<K,V> e; K k;// 如果插入的key一样,并且hash值也相等,如果是,取出这个节点if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;// 如果该节点是代表红黑树的节点,调用红黑树的插值方法else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {// 走到这,说明数组该位置上是一个链表for (int binCount = 0; ; ++binCount) {// 插入到链表的最后面(Java7 是插入到链表的最前面)if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// TREEIFY_THRESHOLD 为 8,所以,如果新插入的值是链表中的第 8 个// 会触发下面的 treeifyBin,也就是将链表转换为红黑树if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;// 判断是否需要扩容if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}

resize方法

思想:用于初始化数组或数组扩容,每次扩容后,容量为原来的 2 倍,并进行数据迁移。

流程图:
在这里插入图片描述

get方法

在这里插入图片描述

源码如下:

public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;
}final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {// 判断第一个节点是不是就是需要的if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;if ((e = first.next) != null) {// 判断是否是红黑树if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);// 链表遍历查找do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}// 找不到返回nullreturn null;
}

常问面试题

HashMap的key可以为 null 嘛?

可以,具体源码如下:

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

当 key 为 null 的时候,hash 值为 0,所以这也是为什么 hashmap 只允许一个 key 为 null

hash 方法的实现及为什么这样实现?

参考上面源码,这样做原因是为了减少高位没有参与下标的计算从而引起的碰撞。

为什么要用异或运算符?

保证了对象的 hashCode 的 32 位值只要有一位发生改变,整个 hash() 返回值就会改变。尽可能的减少碰撞

拉链法导致链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

二叉查找树在特殊情况下会变成一条线性结构(也就是单腿树,这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。红黑树的话,会进行左旋,右旋,变色等操作来保持平衡。引入红黑树就是为了查找数据快,解决链表查询深度的问题

HashMap 存取数据的时间复杂度

不管插入还是查找,由 key 获取 hash 定位到桶的时间复杂度都是 O(1)。
如果桶里面没有元素,那么时间复杂度就是 O(1)。
若有元素,是链表,就要遍历查询,时间复杂度 O(n),如果是红黑树,那么时间复杂度就是 O(logn)。

参考链接:https://blog.csdn.net/LoveMyTail/article/details/107286727

HashMap寻址算法

计算对象的hashCode(),在调用hash()方法进行二次哈希,hashCode值右移16位再异或运算,让哈希分布更为均匀,最后(capacity-1)&hash计算得到索引

为什么HashMap数组长度一定是2的次幂?

计算索引时效率更高:如果是2的n次幂可以使用位与运算代替取模运算
扩容时重新计算索引效率更高:hash & oldCap == 0的元素留在原位置,否则新位置 = 旧位置 + oldCap

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

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

相关文章

IT廉连看——C语言——结构体

IT廉连看——C语言——结构体 一、结构体的声明 1.1 结构的基础知识 结构是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 1.2 结构的声明 struct tag {member-list; }variable-list; 例如描述一个学生&#xff1a;typedef struct Stu…

SQL Server添加用户登录

我们可以模拟一下让这个数据库可以给其它人使用 1、在计算机中添加一个新用户TeacherWang 2、在Sql Server中添加该计算机用户的登录权限 exec sp_grantlogin LAPTOP-61GDB2Q7\TeacherWang -- 之后这个计算机用户也可以登录数据库了 3、添加数据库的登录用户和密码&#xff0…

进程与线程之线程

首先exec函数族是进程中的常用函数&#xff0c;可以利用另外的进程空间执行不同的程序&#xff0c;在之前的fork创建子进程中会完全复制代码数据段等&#xff0c;而exec函数族则可以实现子进程实现不同的代码 int execl(const char *path, const char *arg, ... …

远超 IVF_FLAT、HNSW,ScaNN 索引算法赢在哪?

Faiss 实现的 ScaNN&#xff0c;又名 FastScan&#xff0c;它使用更小的 PQ 编码和相应的指令集&#xff0c;可以更为友好地访问 CPU 寄存器&#xff0c;展示出优秀的索引性能。 Milvus 从 2.3 版本开始&#xff0c;在 Knowhere 中支持了 ScaNN 算法&#xff0c;在各项 benchma…

JavaAPI常用类03

目录 java.lang.Math Math类 代码 运行 Random类 代码 运行 Date类/Calendar类/ SimpleDateFormat类 Date类 代码 运行 Calendar类 代码 运行 SimpleDateFormat类 代码一 运行 常用的转换符 代码二 运行 java.math BigInteger 代码 运行 BigDecimal …

数字孪生的技术开发平台

数字孪生的开发平台可以基于各种软件和硬件工具来实现&#xff0c;这些平台提供了丰富的功能和工具&#xff0c;帮助开发人员构建、部署和管理数字孪生系统&#xff0c;根据具体的需求和技术要求&#xff0c;开发人员可以选择合适的平台进行开发工作。以下列举了一些常见的数字…

将python两个版本添加环境变量(Mac版)

在运行程序的时候&#xff0c;可能不知道选择哪个版本的程序来执行&#xff0c;先添加环境变量&#xff0c;然后进行选择。 1、查看python安装路径 which python which python3 来查看各个版本的安装位置 2、编辑环境变量配置文件 Macos使用默认终端的shell是bash&#xff0c…

c入门第二十三篇: 学生成绩管理系统优化(支持远程操作)

前言 师弟高兴的说道&#xff1a;“师兄&#xff0c;你猜我今天上课看见谁了&#xff1f;” 我&#xff1a;“谁呢&#xff1f;” 师弟&#xff1a;“程夏&#xff0c;没想到&#xff0c;她竟然来旁听我们计算机系的课程了。虽然我从前门进去的&#xff0c;但是我还是一眼就看…

swing jdk版本导致的显示尺寸不一致问题

Java Swing JFrame size different after upgrade to JRE11 from JRE 7 or 8. How can I make the frame size consistent? - Stack Overflow 从 JRE 7 或 8 升级到 JRE11 后&#xff0c;Java Swing JFrame 大小不同。如何使帧大小一致&#xff1f; - IT工具网 设置虚拟机选项…

01背包问题:组合问题

01背包问题&#xff1a;组合问题 题目 思路 将nums数组分成left和right两组&#xff0c;分别表示相加和相减的两部分&#xff0c;则&#xff1a; left - right targetleft right sum 进而得到left为确定数如下&#xff0c;且left必须为整数&#xff0c;小数表示组合不存在&…

28. 找出字符串中第一个匹配项的下标(力扣LeetCode)

文章目录 28. 找出字符串中第一个匹配项的下标题目描述暴力KMP算法 28. 找出字符串中第一个匹配项的下标 题目描述 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。…

mapbox高德地图与相机

mapbox高德地图与相机 本案例使用Mapbox GL JavaScript库创建高德地图。 演示效果引入 CDN 链接地图显示 创建地图实例定义地图数据源配置地图图层 设置地图样式实现代码 1. 演示效果 2. 引入 CDN 链接 <script src"https://api.mapbox.com/mapbox-gl-js/v2.12.0/mapb…

项目实战:Qt监测操作系统cpu温度v1.1.0(支持windows、linux、国产麒麟系统)

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/136277231 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

全面升级!Apache HugeGraph 1.2.0版本发布

图数据库以独特的数据管理和分析能力&#xff0c;在企业数智化转型的过程中正在成为数据治理的核心&#xff0c;根据IDC调研显示&#xff0c;95%的企业认为图数据库是重要的数据管理工具&#xff0c;超过65%的厂商认为在业务上图数据库优于其他选择&#xff0c;尤其是在金融风控…

Unity零基础到进阶 | Unity中的 RectTransformUtility 方法整理汇总

Unity零基础到进阶 ☀️| RectTransformUtility 方法整理汇总一、RectTransformUtility 官方文档1.1 RectTransformUtility.CalculateRelativeRectTransformBounds&#xff08;重&#xff09;1.2 RectTransformUtility.FlipLayoutAxes1.3 RectTransformUtility.FlipLayoutOnAxi…

观察者模式与发布订阅模式

观察者模式 定义&#xff1a; 观察者模式是一种行为型设计模式&#xff0c;定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 结构图&#xff1a; ES6简易代码实现&#xff1a; //ts环境下…

Nginx——安装和反向代理

Nginx安装与应用 1.1 Nginx介绍 Nginx 是一个高性能的HTTP和反向代理服务器,特点是占有内存少&#xff0c;并发能力强 Nginx可以作为静态页面的web服务器&#xff0c;同时还支持CGI协议的动态语言&#xff0c;比如perl、php等。但是不支持java。Java程序只能通过与tomcat配合…

谷歌AI发展史:从阿尔法围棋到Gemini与Gemma的开源创新

谷歌一直是人工智能领域的重要推动者。本文将回顾谷歌AI的发展历程&#xff0c;从阿尔法围棋到现如今的Gemini和Gemma&#xff0c;探讨谷歌在人工智能领域的重大突破和创新。 1. 引言 在计算机科学领域&#xff0c;谷歌一直是人工智能&#xff08;AI&#xff0…

MasterAlign全景视觉点胶应用软件说明书

MasterAlign视觉软件通过高精度的图像处理和机器学习算法&#xff0c;实现了对点胶过程的全面控制和管理。以下是关于MasterAlign在全景视觉点胶应用场景中如何使用的详细说明。看完全文相信一定能让您快速上手使用。

多维时序 | Matlab实现CPO-BiTCN-BiGRU冠豪猪优化时间卷积神经网络双向门控循环单元多变量时间序列预测模型

多维时序 | Matlab实现CPO-BiTCN-BiGRU冠豪猪优化时间卷积神经网络双向门控循环单元多变量时间序列预测模型 目录 多维时序 | Matlab实现CPO-BiTCN-BiGRU冠豪猪优化时间卷积神经网络双向门控循环单元多变量时间序列预测模型预测效果基本介绍程序设计参考资料 预测效果 基本介绍…