hashMap加载因子
加载因子
加载因子是用来判断当前HashMap<K,V>中存放的数据量,默认的加载因子是0.75。
加载因子比较大,扩容发生的频率比较低,浪费的空间比较小,发生hash冲突的几率比较大。
比如,加载因子是1的时候,hashmap长度为128,实际存储元素的数量在64至128之间时间段比较多,这个时间段发生hash冲突比较多,造成数组中其中一条链表比较长,会影响性能。
加载因子比较小,扩容发生的频率比较高,浪费的空间比较多,发生hash冲突的几率比较小。
比如,加载因子是0.5的时候,hashmap长度为128,当数量达到65的时候会触发扩容,扩容后为原理的256,256里面只存储了65个,浪费了。综合了一下,取了一个平均数0.75作为加载因子。
长度恒定为2的n次方
HashMap的数组长度恒定为2的n次方,也就是说只会为16,32,64这种数。即便你给的初始值是13,最后数组长度也会变成16,它会取你传进来的数,最近一个2的n次方的数。这么设计的目的主要是为了解决底层运算后的值可以落到数组的每个下标上面。
hashMap获取索引的方法:
//indexFor中的h是hashCode通过变换之后的值,是一个32位的二进制数
public static int indexFor(int h, int length) {
return h & (length-1);
}
HashMap中运算数组的位置,使用的是length-1,每次扩容会把原数组的长度*2,在二进制上的表现就是高位进1,并且后四位始终都是1111。
初始长度为16的数组,对应的length-1就是15,原数组15二进制后八位为0000 1111。
扩容后的长度为32的数组,对应的length-1就是31,二进制就变成了0001 1111。
再次扩容长度为64的数组,对应的length-1就是63,二进制是0011 1111。
1
2
3
假设hashMap容量为16
hash值&运算:
11001110 11001111 00010011 11110001(hash值)
&
00000000 00000000 00000000 00001111(16-1的2进制)
00000000 00000000 00000000 00000001
hash的2进制的后4位和1111比较,hash值的后4位范围是0000-1111之间,与上1111,最后的值是在0000-1111,也就是0-15之间。这样就保证运算后的值可以落到数组的每一个下标。
如果数组长度不是2的幂次,后四位就不可能是1111,0000~1111的一个数和有可能不是1111的数进行&运算,数组的某几位下标就有可能永远不会有值,这就没法保证运算后的值可以落到数组的每个下标上面。
散列均匀分布
hashMap获取索引的indexFor方法里面的h是hashCode通过变换之后的值,是一个32位的二进制数,如果直接用如此长的二进制数和目标length-1直接进行与运算,结果会导致高位会大量丢失。
假如我们以16位为划分,任何两个高16位不一样,低16位一样的数。这两个数的hashCode与length-1做与运算(hashCode & length-1),结果会是一样的,这样的两个数,却产生了相同的hash结果,发生hash冲突。
于是hashMap想到了一种处理方式:底层算法通过让32位hashcode中保持高16位不变,高16与低16异或结果,作为新的低16位,然后用hash得到的结果(int h)传入方法indexFor获取到hashMap的索引。
计算中只有低位16位参与&运算,计算效率高,同时也保证的hash的高16位参与了索引运算,这样得到的索引能呈较为理想的散列分布,在将条目放入hashMap中时,最大限度避免hash碰撞。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//把hash值异或了hash值右移16位,即取高16位
}
绝大多数情况下length一般都小于2^16即小于65536,所以indexFor方法中return h & (length-1)的结果始终是h的低16位与(length-1)进行&运算。hashmap为了考虑性能的设计还是非常精妙的。
- 点赞
- 收藏
- 关注作者
评论(0)