手撕HashMap底层源码(学习内容全)

day28上

集合框架

标绿已经学习底层,深入底层主要是研究实现类底层
集合框架图

手撕HashMap底层源码

JDK1.7版本的HashMap为例(注意实验代码时进行版本切换)
代码注释参考理解

//day27初识
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{//默认初始化容量 -- 必须是2的幂static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//最大容量static final int MAXIMUM_CAPACITY = 1 << 30;//默认的负载因子static final float DEFAULT_LOAD_FACTOR = 0.75f;//空内容的数组static final Entry<?,?>[] EMPTY_TABLE = {};//hash数组/hash表transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//new Entry[16];//元素个数transient int size;//4//阈值(数组长度*负载因子)int threshold;//12//负载因子final float loadFactor;//0.75f//外部操作数(记录添加、删除的次数)transient int modCount;//4//hash种子数transient int hashSeed = 0;//0public HashMap() {this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);}//initialCapacity - 16//loadFactor - 0.75fpublic HashMap(int initialCapacity, float loadFactor) {//判断数组初始化容量如果小于0,就报错if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);//判断数组容量大于最大容量,就把最大容量赋值给初始化容量if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;//判断负载因子如果小于等于0 或者 判断负载因子不是一个数字,就报错if (loadFactor <= 0 || Float.isNaN(loadFactor))//NaN - Not a Numberthrow new IllegalArgumentException("Illegal load factor: " + loadFactor);this.loadFactor = loadFactor;threshold = initialCapacity;init();//作用:让子类去重写(LinkedHashMap),子类做初始化功能}void init() {}//day28上:添加过程代码底层//key - null//value - "bbb"public V put(K key, V value) {//第一添加时,进入的判断if (table == EMPTY_TABLE) {//1.计算出阈值 -- 12//2.初始化hash数组 -- new Entry[16]//3.初始化hashSeed(Hash种子数)inflateTable(threshold);}if (key == null)return putForNullKey(value);//通过key获取hash值 -- 20int hash = hash(key);//利用key的hash值计算在数组中的下标 -- 4int i = indexFor(hash, table.length);//判断当前下标上是否有元素 -- 进入到该循环就说明hash碰撞了for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;//判断key和Entry中的key是否相同(hash && == || equals)if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//oldValue - 玩游戏V oldValue = e.value;//e.value - 写代码e.value = value;e.recordAccess(this);return oldValue;//返回被替换的值}}modCount++;addEntry(hash, key, value, i);return null;}//value - "bbb"private V putForNullKey(V value) {//判断下标为0的位置上是否有元素 -- 进入到该循环就说明hash碰撞了for (Entry<K,V> e = table[0]; e != null; e = e.next) {//判断Entry里的key是否为空,说明下标为0的位置上可能会存储其他key不为空的Entry对象if (e.key == null) {//oldValue - aaaV oldValue = e.value;//e.value - bbbe.value = value;e.recordAccess(this);return oldValue;//返回被替换的值}}modCount++;addEntry(0, null, value, 0);return null;}//子类的挂钩:让子类(LinkedHashMap)重写的方法void recordAccess(HashMap<K,V> m) {}//hash - //key - //value - //bucketIndex - void addEntry(int hash, K key, V value, int bucketIndex) {//判断元素个数大于等于阈值并且当前下标的元素不为null,就扩容if ((size >= threshold) && (null != table[bucketIndex])) {//扩容 -- 原来数组长度的2倍resize(2 * table.length);//通过key重新计算hash值hash = (null != key) ? hash(key) : 0;//通过hash值重新计算在数组中的下标bucketIndex = indexFor(hash, table.length);}createEntry(hash, key, value, bucketIndex);}//newCapacity - 32void resize(int newCapacity) {//获取tableEntry[] oldTable = table;//oldCapacity - 16int oldCapacity = oldTable.length;//如果数组长度已经达到数组的最大值(1<<30)//就将int类型的最大值赋值给阈值,并且结束当前方法//目的:以后大概率不会再次调用resize()if (oldCapacity == MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return;}//newTable = new Entry[32];Entry[] newTable = new Entry[newCapacity];//1.initHashSeedAsNeeded(newCapacity) --重新计算hash种子数//2.将table的Entry数据赋值给newTabletransfer(newTable, initHashSeedAsNeeded(newCapacity));//将newTable的内存地址赋值给tabletable = newTable;//重新计算阈值:threshold-24threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);}//newTable - new Entry[32];void transfer(Entry[] newTable, boolean rehash) {//newCapacity - 32int newCapacity = newTable.length;//遍历hash数组for (Entry<K,V> e : table) {while(null != e) {Entry<K,V> next = e.next;if (rehash) {e.hash = null == e.key ? 0 : hash(e.key);}int i = indexFor(e.hash, newCapacity);e.next = newTable[i];newTable[i] = e;e = next;}}}//hash - 0//key - null//value - "aaa"//bucketIndex - 0void createEntry(int hash, K key, V value, int bucketIndex) {//e - nullEntry<K,V> e = table[bucketIndex];//JDK1.7版本的HashMap是头插法table[bucketIndex] = new Entry<>(hash, key, value, e);size++;}//h - 20//length - 16static int indexFor(int h, int length) {//20 -- 0001,0100//15 -- 0000,1111//		0000,0100//    20 & (16-1)return h & (length-1);}//k - new Student("小小", '男', 23, "2401", "001")final int hash(Object k) {//获取hash种子数int h = hashSeed;//判断种子数不等于0 并且 k的类型为Stringif (0 != h && k instanceof String) {//利用stringHash32()计算字符串的hash值(目的:减少hash碰撞)return sun.misc.Hashing.stringHash32((String) k);}h ^= k.hashCode();h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);}//toSize - 16private void inflateTable(int toSize) {// 2的幂的数字的特点:在二进制表示中只有一位为1,其余全是0//toSize-16,返回16//toSize-19,返回32//toSize-30,返回32// capacity - 16int capacity = roundUpToPowerOf2(toSize);//threshold - 12//threshold = (int) Math.min(16 * 0.75f, (1<<30) + 1);threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);//初始化hash数组 --  new Entry[16];table = new Entry[capacity];//初始化hash种子数initHashSeedAsNeeded(capacity);}final boolean initHashSeedAsNeeded(int capacity) {boolean currentAltHashing = hashSeed != 0;boolean useAltHashing = sun.misc.VM.isBooted() &&(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);boolean switching = currentAltHashing ^ useAltHashing;if (switching) {hashSeed = useAltHashing? sun.misc.Hashing.randomHashSeed(this): 0;}return switching;}//number - 16private static int roundUpToPowerOf2(int number) {// 保留二进制中最高位的1,其余变成0// Integer.highestOneBit((number) << 1)return number >= MAXIMUM_CAPACITY? MAXIMUM_CAPACITY: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;}//映射关系类/节点类static class Entry<K,V> implements Map.Entry<K,V> {final K key; --------- keyV value; ------------- valueEntry<K,V> next; ----- 下一个节点的地址int hash; ------------ key的hash值Entry(int h, K k, V v, Entry<K,V> n) {value = v;next = n;key = k;hash = h;}}
}
场景:HashMap<Student, String> map = new HashMap<>();map.put(new Student("小小", '男', 23, "2401", "001"), "拍电影");map.put(new Student("大大", '男', 20, "2401", "002"), "打篮球");map.put(new Student("奇男子", '男', 21, "2401", "003"), "玩游戏");map.put(new Student("奇男子", '男', 21, "2401", "003"), "写代码");map.put(null, "aaa");map.put(null, "bbb");
//补充:Student类中重写的hashCode()和equals()//项目中如何编写hashCode,根据判断两个对象的条件去制定hash值的算法@Overridepublic int hashCode() {return classId.hashCode() + id.hashCode();}@Overridepublic boolean equals(Object obj) {if(this == obj){return true;}if(obj instanceof Student){Student stu = (Student) obj;if(this.classId.equals(stu.classId) && this.id.equals(stu.id)){return true;}}return false;}

HashMap理解图

 HashMap理解图

init();的作用

让子类去重写(LinkedHashMap),子类做初始化功能
伪代码理解:LinkedHashMap调用父类的有参构造,int()返过来调用子类LinkedHashMap中重写的int();
init();的作用

面试题

JDK1.7版本的HashMap是什么数据结构?

一维数组+单向链表

什么是Hash桶?

hash数组里的单向链表

什么是hash碰撞/hash冲突?

key的hash值一致,在数组中的下标上有重复的元素

HashMap里的hash碰撞是如何优化的?

根据需求重写hashCode(),尽可能保证hash值不相同,减少hash碰撞的次数

HashMap默认数组长度是多少?

长度是1<<4,就是16的长度

HashMap数组的长度为什么必须是2的幂?

2的幂的数字的特点为二进制中只有1位为1,其余为0(16–0001,0000)

2的幂的数字-1的特点为二进制中原来为1的位置变为0,后续的位置全变成1(15–0000,1111)

计算key在数组中的下标的算法:hash值 & 长度-1

如果数组长度不是2的幂会导致散列不均匀

HashMap数组的最大容量是多少?

1<<30

HashMap数组的最大容量为什么是1<<30?

最大容量为int类型,int类型的最大值是2的31次方-1

因为HashMap数组必须是2的幂,1<<30是int取值范围内最大的2的幂的数字

所以HashMap数组最大容量是1<<30

HashMap默认负载因子是多少?

0.75f

HashMap的负载因子的作用是什么?

数组长度*负载因子 等于 阈值,阈值是控制何时扩容

HashMap数组默认的负载因子为什么是0.75f?

取得了空间和时间的平衡

如果负载因子过大(1),会导致数组全部装满后,再扩容。利用了空间,浪费了时间

如果负载因子过小(0.2),会导致数组装了一点点元素,就扩容。利用了时间,浪费了空间

HashMap何时扩容?

元素个数大于等于阈值并且当前下标的元素不为null,就扩容

HashMap扩容机制是什么?

原来长度的2倍

HashMap存放null键的位置在哪?

hash数组下标为0的位置

HashMap的hash回环/死循环是何时发生的?

在多线程的情况下,一个线程不断的添加数据,导致扩容,链表地址发生回环。一个线程不断的遍历数据。

如果发生hash回环应该是程序员负的责任,因为HashMap明确表示该实现不是一个线程安全的,多线程下应该使用ConcurrentHashMap

JDK1.7的HashMap和JDK1.8的HashMap有什么区别:

区别1 - 获取key的hash值:

​ JDK1.7 – 调用key的hashCode() + 位运算

​ JDK1.8 – 将key的hash值(int-32)分为高16位和低16位,两者进行异或的位运算,比之前更简洁

区别2 - 插入链表的法则:

​ JDK1.7 – 头插法

​ JDK1.8 – 尾插法

区别3 - 数据结构:

​ JDK1.7 – 一维数组 + 单向链表

​ JDK1.8 – 一维数组 + 单向链表 + 红黑树(目的:加上红黑树提高查询效率)

JDK1.8版本的HashMap数据结构是如何切换的?

初始数据结构为一维数组 + 单向链表

当一维数组长度大于64并且单向链表长度大于8时 --> 一维数组 + 红黑树

当链表长度小于6时 --> 一维数组 + 红黑树 转换为一维数组 + 单向链表

JDK1.8的HashMap为什么链表长度大于8会将单向链表转换为红黑树?

为了提高查询效率,大于8是因为泊松分布

总结:

注重面试题

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

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

相关文章

SpringBoot3整合Mybatis-Plus与PageHelper包冲突解决

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; SpringBoot3整合Mybatis-Plus与PageHelper包冲突解决 ⏱️ 创作时间&a…

Elasticsearch - Docker安装Elasticsearch8.12.2

前言 最近在学习 ES&#xff0c;所以需要在服务器上装一个单节点的 ES 服务器环境&#xff1a;centos 7.9 安装 下载镜像 目前最新版本是 8.12.2 docker pull docker.elastic.co/elasticsearch/elasticsearch:8.12.2创建配置 新增配置文件 elasticsearch.yml http.host…

大模型时代,微软AI投资的布局

这些领域涉及 3D、代码、销售、游戏等多个行业。其中&#xff1a; 在 3D 领域&#xff0c;blackshark.ai 利用 AI 技术提供地理空间数据解决方案&#xff1b;humane 专注于人机交互技术创新&#xff1b;Builder.ai 提供了无需编程知识的应用构建平台。代码方面&#xff0c;GitH…

Java基础学习笔记三

环境变量CLASSPATH classpath环境变量是隶属于java语言的&#xff0c;不是windows操作系统的&#xff0c;和PATH环境变量完全不同classpath环境变量是给classloader&#xff08;类加载器&#xff09;指路的java A 。执行后&#xff0c;先启动JVM&#xff0c; JVM启动classload…

GIS学习

匹配查询&#xff0c;先连接两个表&#xff0c;然后在一个表里面查询 合并两个形状 比较好的colormap http://soliton.vm.bytemark.co.uk/pub/cpt-city/views/totp-cpt.html https://docs.gmt-china.org/latest/cpt/builtin-cpt/ 计算坡度时就要捕捉栅格 重分类时也要捕捉栅…

数据结构:10、排序

本文将会介绍8种排序&#xff0c;并在文章末附上代码 一、排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;…

[蓝桥杯 2019 省 A] 外卖店优先级

模拟 双指针 #include<iostream> #include<algorithm> using namespace std; using ll long long; #define int long long const int N 1e510; const int inf 0x3f3f3f3f; const int mod 1e97;int n,m,ts;bool vis[N]; int a[N]; int last[N]; pair<int,int…

外卖项目:菜品管理功能代码实现(debug)

文章目录 一、菜品管理功能代码实现1、新增菜品2、菜品分页查询3、修改菜品4、菜品的起售与停售5、删除菜品 一、菜品管理功能代码实现 1、新增菜品 这涉及到多张表&#xff0c;要用事务 添加成功 2、菜品分页查询 3、修改菜品 该页面共涉及4个接口。 接口&#xff1a; 根…

protobuf原理解析-基于protobuf-c实现序列化,反向序列化

1.一个实例 前面介绍了使用protobuf的流程&#xff0e; (1). 定义proto文件来描述需要序列化和反向序列化传输的消息&#xff0e; (2). 借助proto-c&#xff0c;为proto文件生成对应的代码控制文件&#xff0e; (3). 程序借助生成的代码控制文件和protobuf-c动态库的支持实现类…

如何写出干净的 Git Commit

大家好&#xff0c;我是楷鹏。 写一份干净的 Git Commit&#xff0c;不仅赏心悦目&#xff0c;也有诸多好处&#xff0c;比如 为项目或者仓库生成 change log方便在其他一些 Git 工具使用&#xff0c;比如 CI/CD、代码协作和审计平台、发版工具等 这是 AngularJS 仓库的 Git …

短视频矩阵系统技术交付

短视频矩阵系统技术交付&#xff0c;短视频矩阵剪辑矩阵分发系统现在在来开发这个市场单个项目来说&#xff0c;目前基本上已经沉淀3年了&#xff0c;那么我们来就技术短视频矩阵剪辑系统开发来聊聊 短视频矩阵系统经过315大会以后&#xff0c;很多违规的技术开发肯定有筛选到了…

[ C++ ] STL---string类的使用指南

目录 前言&#xff1a; string类简介 string类的常用接口 string类对象的构造函数 string类对象的赋值运算符重载 string类对象的容量操作 string类对象的访问与遍历 [ ] 下标遍历 迭代器遍历 普通迭代器iterator ​编辑 const迭代器const_iterator 反向迭代器rever…

常见的WAFI攻击包括哪些

WIFI攻击是指黑客利用各种手段&#xff0c;对WIFI网络进行非法入侵和攻击&#xff0c;以获取用户的个人信息、网络权限或其他敏感数据。常见的WIFI攻击方式主要包括以下几种&#xff1a;伪造认证页面&#xff1a;黑客可以创建一个伪造的认证页面&#xff0c;当用户尝试连接到WI…

Vue2(三):绑定样式、条件渲染(v-if,v-show)、列表渲染(v-for)、key的原理、列表过滤、列表排序

一、绑定样式 1.绑定class样式 (1)字符串写法 适用于&#xff1a;样式类名不确定&#xff0c;需要动态获取。 <div id"root"><div class"basic" :class"mood" click"changeMood">test</div><!-- class是原本的…

Android Studio实现内容丰富的安卓旅游景点预定

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动 1.开发环境 android stuido3.6 jak1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看景点列表 3.查看景点详情 4.景点预定 5.购物车支付结算功能 6…

计算机网络:计算机网络概述

计算机网络&#xff1a;计算机网络概述 因特网概述网络&#xff0c;互连网&#xff0c;因特网因特网发展的三个阶段因特网的标准化工作因特网组成 计算机网络的定义计算机网络的分类按使用者分类按传输介质分类按网络的覆盖范围分类按拓扑结构分类 因特网概述 网络&#xff0c…

红外相机和RGB相机标定:实现两种模态数据融合

1. 前期准备 RGB相机&#xff1a;森云智能SG2-IMX390&#xff0c;1个红外相机&#xff1a;艾睿光电IR-Pilot 640X-32G&#xff0c;1个红外标定板&#xff1a;https://item.taobao.com/item.htm?_ujp3fdd12b99&id644506141871&spma1z09.2.0.0.5f822e8dKrxxYI 2.操作步…

目标检测---IOU计算详细解读(IoU、GIoU、DIoU、CIoU、EIOU、Focal-EIOU、SIOU、WIOU)

常见IoU解读与代码实现 一、✒️IoU&#xff08;Intersection over Union&#xff09;1.1 &#x1f525;IoU原理☀️ 优点⚡️缺点 1.2 &#x1f525;IoU计算1.3 &#x1f4cc;IoU代码实现 二、✒️GIoU&#xff08;Generalized IoU&#xff09;2.1 GIoU原理☀️优点⚡️缺点 2…

网络编程:数据库

一、作业 1> 创建一个工人信息库&#xff0c;包含工号&#xff08;主键&#xff09;、姓名、年龄、薪资。 2> 添加三条工人信息&#xff08;可以完整信息&#xff0c;也可以非完整信息&#xff09; 3> 修改某一个工人的薪资&#xff08;确定的一个&#xff09; 4> …

SAP上线计划Cutover Plan

在SAP项目中&#xff0c;上线计划Cutover Plan(另一说法是切换计划)是指在项目的最后阶段&#xff0c;即从旧系统过渡到新SAP系统的过程中&#xff0c;组织必须执行的一系列活动和步骤的详细计划。这个计划对于确保平稳、有序的系统过渡至关重要。Cutover计划通常涵盖了组织沟通…