
元素在数组中的位置由key.hashCode()的值决定,如果两个key的哈希值相等,即发生了哈希碰撞,用链地址法解决 Hash 碰撞
当然这张图中没有体现出来的有两点:
- 为了提升整个HashMap的读取效率,当 HashMap 中的元素个数超过 数组大小*负载因子 时 扩容,以减小哈希碰撞。
- 在 Java 8 中如果桶数组的同一个位置上的 链表数量超过一个定值,则整个链表有一定概率会转为一棵红黑树。
题外,解决 Hash 碰撞的其它方法:
1、ReHash :多个次选哈希函数。在发生冲突时,再用第二个,第三个...哈希函数算出哈希值,直到算出的哈希值不同为止。虽然不易发生聚集,但增加了计算时间。
2、开放地址法(再散列法):Hi=(H(key)+di) MOD m i=1,2,…,k (k<=m-1)
- 如果di值可能为1,2,3,…m-1,称线性探测再散列。如果di取1,则每次冲突之后,向后移动1个位置。
- 如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-kk(k<=m/2),称二次探测再散列。
- 如果di取值可能为伪随机数列。称伪随机探测再散列。
怎么样才是一个好的hash函数呢?
- 计算出来的哈希值足够散列,能够有效减少哈希碰撞
- 本身能够快速计算得出,因为HashMap每次调用get和put的时候都会调用hash方法
怎么计算初始桶容量的大小?怎么实现大小为大于 cap 的最小 2 的整数幂次方?
找到大于指定cap的最小的2的整数幂。
>>> 无条件右移符号,|= 代表布尔值或。
假设 n 为 0001 xxxx
0001 xxxx 进行 n >>> 1 为 0000 1xxx ,0001 xxxx |= 0000 1xxx 为 0001 1xxx
0001 1xxx 进行 n >>> 2 为 0000 011x ,0001 1xxx |= 0000 011x 为 0001 111x
0001 111x 进行 n >>> 4 为 0000 0001,0001 111x |= 0000 0001 为 0001 1111
.......
最后 n 会变为 0001 1111 ,低位全为 1
return 的时候再 +1,变为 0010 0000 ,即大于指定cap的最小的2的整数幂。
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; }
如果没有指定 cap 大小,bucket 的默认大小是多少?扩容的阈值负载因子是多少?
默认大小是 16,发生扩容的默认负载因子是 0.75。也就是说,默认情况下,那么当HashMap中元素个数超过 16*0.75=12 的时候,就把数组的大小扩展为 2*16 = 32,即扩大一倍。
HashMap 什么时候开辟 bucket 数组占用内存?
在HashMap第一次put的时候,无论Java 8还是Java 7都是这样实现的。这里我们可以看到两个版本的实现中,桶数组的大小都是2的正整数幂。
HashMap 内部的 bucket 数组长度为什么要为2的整数次幂?
第一
通过哈希函数得到哈希计算结果后,把这个结果放在桶的哪个位置,要除以桶的大小取余
这个算法实际就是取模,hash % length,计算机中 直接求余效率远不如位移运算,源码中做了优化hash & (length - 1 )
hash % length == hash & ( length - 1 ) 的 前提是length是2的n次方
例如 14 除以 8 取余结果为 6 , 相当于 1110 & 0111 (7)= 0110
例如 长度是 0000 0000 1000 0000 哈希结果对它 -1 取余就是 0010 1010 0101 0001 & 0000 0000 0111 1111 = 0000 0000 0101 0001
相当于舍弃了哈希结果,自长度 2的 n 次以上的高位信息,保留了低位信息
第二
如果在put数据之后超过了threshold的值,则需要扩容,扩容意味着桶数组大小变化。HashMap寻址是通过对列表大小求余来计算的,现在列表大小发生了变化,势必会导致部分key的位置也发生了变化,HashMap是如何设计的呢?
这里就涉及到桶数组长度为2的正整数幂的第二个优势了:当桶数组长度为2的正整数幂时,如果桶发生扩容(长度翻倍),则桶中的元素大概只有一半需要切换到新的桶中,另一半留在原先的桶中就可以,并且这个概率可以看做是均等的。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek “源神”启动!「GitHub 热点速览」
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器