前提
HashMap是有数组+链表组成的,其中使用的算法有:hash(java8又使用了红黑树)
loadFactor
- loadFactor是参与计算HashMap扩容的一个加载因子
- new HashMap()会默认给loadFactor加载一个值0.75
capacity
- capacity并非HashMap的属性,指的是HashMap数组的大小,即table.length
threshold
- threshold是判断HashMap是否扩容的阈值
- 官方注解上写的是 threshold = capacity * loadFactor
put时,数组下标计算
此处特别感谢zhangzhikai1同学的提醒
hash函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
putVal函数
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
- 汇总为:table下标i=(table.length- 1) & ((h = key.hashCode()) ^ (h >>> 16))
- 当我们put的时候,会根据key获取对应的hash值,然后无符号右移16位(>>> 16),在与原本的hash值进行^计算,然后再与table.length-1进行&计算,最终得出需要放入的位置
- 比如:key = “165156”,HashMap中table数组的大小为16,套入公式,那么我们就会计算出存放位置的下标为13
- 通过这种方式计算出的值只能是0–15,不行你自己试试
- 我们来看一下为什么
核心计算
- (16-1)的二级制:0000 0000 0000 1111
- &的计算方式是:同为1时为1,否则为0
- 0000 0000 0000 1111的前12位都为0,那么和任何一个值进行&计算,最终得到的结果前12位只能为0
- 0000 0000 0000 1111的后4位都是1,那么和任何一个值进行&计算,最终得到的结果还是原来的值
- 最终的结果就是,把原有值的前12位归0,只保留后4位,最终转成十进制,结果集只能是0-15之间
- 当前HashMap的table数组长度为16,下标值为0-15,膜拜我Doug Lea大神的神操作
扩容时,下标的重置计算
- 在resize函数中,原HashMap的table数组扩容一倍,那么数组,以及链表中的对象,都需要重新分配位置
- 那么在重新计算数组及链表中的位置时,情况分为三种
- 第一种情况:数组有值,链表无值,这个时候只需要重新计算位置,放进去就OK了(e.hash & (newCap - 1))这个计算方式,就是上文数组下标计算,这里有个骚操作,可以结合第三种情况一起说
- 第二种情况:数组有值,链表无值 ,但是红黑树有值,
- 第三种情况:数组有值,链表有值,业务处理逻辑:循环列表判断每一个元素是否需要换位置,不需要的重新组合成链表,放入当前数组下标中,如果需要的,组成一个链表,放入指定的数组下标中
- 第三种情况中判断元素是否需要换位置的依据是什么?if ((e.hash & oldCap) == 0) ,是这个判断,这个判断中e.hash指的是当前元素的hash值,oldCap指的是当前table数组的大小
- 比如我们存放16个元素,而table数组的长度为4(原本默认为16,太大了,举例费劲),在假设16个元素的hash分别为0,1,2,3…一直到15,那么我们存放到table数组中的位置如下
下标 | 0 | 1 | 2 | 3 |
---|---|---|---|---|
数组中的元素 | e0(hash值为0) | e1(hash值为1) | e2(hash值为2) | e3(hash值为3) |
链表中的元素 | e4(hash值为4) | e5(hash值为5) | e6 (hash值为6) | e7 (hash值为70) |
链表中的元素 | e8(hash值为8) | e9(hash值为9) | e10(hash值为10) | e11 (hash值为11) |
链表中的元素 | e12 (hash值为12) | e13(hash值为13) | e14(hash值为14) | e15 (hash值为15) |
- 开始循环数组,获取到0下标,循环0下标中的元素(e0,e4,e8,e12)
- e0,套入公式if((e.hash & oldCap) == 0) ------------ if((0&4)==0) ---------- true
- e4,套入公式if((e.hash & oldCap) == 0) ------------ if((4&4)==0) ---------- false
- e8,套入公式if((e.hash & oldCap) == 0) ------------ if((8&4)==0) ---------- true
- e12,套入公式if((e.hash & oldCap) == 0) ------------ if((12&4)==0) ---------- false
- e4,e12,是有需要移动的,那么移动的位置,我们也可以计算出来,是扩容后的下标为4的位置
- 这既是Doug Lea的有一个神操作了,竟然都是需要移动到下标为4的地方,喊一波666。位置计算方式:数组下标计算
- 而且基本上每个下标需要移动的数据n/2,取整,移动的位置刚好是当前位置i+扩容的差,如当前就是:i+4