HashMap底层
HashMap底层
主要jdk1.8中
主要数据结构为:数组+链表+红黑树
构造HashMap后,table一开始为空,默认loadfactor=0.75,
size;//键值对个数
hash方法
右移16位再按位异或
DEFAULT_INITIAL_CAPACITY = 1 << 4;
DEFAULT_LOAD_FACTOR = 0.75f;//扩容阈值,初始化默认0.75
TREEIFY_THRESHOLD = 8;
UNTREEIFY_THRESHOLD = 6;
MIN_TREEIFY_CAPACITY = 64;
transient Node<K,V>[] table;//Hashmap中数组槽
transient int size;//键值对元素个数
- 先扰动
- 再hashcode
put方法
putval()--resize()
- 最开始table数组为空,去resize()-->指定默认数组容量--默认加载因子
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
2.封装好new Node(hash,key,value,next)再放入到数组中
计算数组下标(n-1)&hash 此下标当前位置没有值,则把这个新节点放进去
若此数组下标处已经有元素
hashmap插入流程
-
数组为空,初始化数组,指定容量为16,loadfactor=0.75
-
将key --value封装成Node内部类,Node是单向链表
-
计算hash,通过hash去计算数组下标
-
当前数组下标中没有元素,直接放到该下标处
4.1当前数组下标中如果有元素,判断该元素与传入元素的hash以及key是否相同,若相同则覆盖
4.2若该数组下标中为红黑树节点,执行红黑树插入
4.2若数组下标有元素且4.1,4.2不成立,则for循环遍历链表,判断下个元素,如果是尾节点直接放入,如果下个元素与传入元素的hash以及key是否相同,若相同则覆盖
4.3判断是否要树化,bigcount++>=7? 树化条件 链表元素个数大于8,且数组长度为64
++size是否大于扩容阈值,resize()方法
hash方法
右移16位再异或运算
让key的hashcode高16与低16都参与到数组下标计算过程中去
高位没起到作用,会导致hash冲突变大
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashMap始终控制数组长度位2的次方数?
tableSizefor()方法控制
就算初始化HashMap传入的参数容量为10,也会初始化后变为10;
为什么要用(n-1)&hash?
其实要计算数组下标最简单是用计算后的扰动hash对数组长度进行取余操作
hash%16=0-15;
- 计算机底层 进行位运算更快
- hash%n==(n-1)&hash
get方法
get()方法流程总结:
- 首先判断桶数组是否为空,若为空直接返回null。
- 若桶数组不为空,则计算索引,查看当前索引是否有元素,若有元素,查看key值是否相同。若key值相同,则返回该节点。
- 若key值不相同,则继续查找,先判断是否树化,若已经树化,则调用红黑树的getTreeNode()进行查找。
- 若没有树化,则从前往后遍历链表,找到了则返回节点,找不到则返回null。
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);
}
}
return null;
}
-
为什么我们不用hashCode()方法计算的出来的hash值作为桶下标呢?
因为hashCode()计算出来的hash值太大,需要大量的存储空间;而且这样哈希表就跟普通的数组差不多。 -
为什么在hash()中计算桶下标要进行(h>>>16)?
这样保留了高16位,使得高低16位都参与异或运算,降低了哈希冲突的概率。
树化的条件:
- 当前桶中的链表长度>=8,并且哈希表的长度>=64时,会树化,否则只是进行简单的扩容。
- 加入红黑树是为了提高由于链表过长而造成的查询效率降低,将时间复杂度由O(n)提升为O(logn)。
5.扩容的条件:当桶数组的容量>12时进行扩容。
6.解树化的条件:当红黑树的节点个数<=6时,会将红黑树转为链表。
resize方法
有两种情况会触发扩容
- ++size>threshold 16*0.75=12
- 链表长度大于8且数组长度小于64
数组总是两倍扩容,遍历数组的每个下标位置,原数组中会有四种情况
-
数组中为空 直接跳过
-
数组中为单个Node 计算新下标,放到新数组中去
-
数组中为链表
分为高低链插入新数组的下标位置
` if ((e.hash & oldCap) == 0)//低位置下标插入 newTab[j] = loHead;
if ((e.hash & oldCap) == 1)//高位置下标插入 newTab[j + oldCap] = hiHead;`
-
数组中为红黑树节点 split()方法
考虑何时反树化 重新计算下标,高低节点分开后,一侧小于6个节点,TreeNode转变为Node
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY