HashMap

HashMap

1.为什么hashmap长度必须是2的幂

hashmap插入源码代码如下

p = tab[i = (n - 1) & hash]

数据的hash值一般都很长(各数据类型hashCode源码解读在这里),hashmap需要根据hash值将数据插入到对应的桶中,

例如某个数的hash值是011001101010,若hashmap长度为16,那么hashMap中的桶编号就是0-15(0000-1111),二进制表示需要4位,那么hashmap只需要hash值的后四位作为桶编号,

如果hashmap的长度是2的幂,长度减1刚好是覆盖所有桶编号且全为1的二进制数,全为1的二进制数与hash值作与运算不会对原有值做任何改动,只是取出hash值的后n位.

例如hashmap长度为16,那么16-1=15=1111,1111与任何数作与运算都只会取出其后四位.

但若数组长度为15,hashcode值会与14(1110)进行“与”,那么最后一位永远是0,编号为0001,0011,0101,1001,1011,0111,1101的桶永远都不会存放元素,空间浪费相当大

也可以使用取余%运算,那样就不用考虑2的幂,但是%运算速度是很慢的,考虑到hashmap经常查存,性能会下降很多

2.为什么默认初始长度是16

前面我们说了为什么hashmap大小一定是2的幂,hashmap大小是8或者4的话很容易导致map扩容影响性能,分配的太大的话又会浪费资源,所以就使用16作为初始大小

3.为什么每次长度增加是乘2

问题1已经回答了

4.如果自定义初始长度不是2的次方,冲突会增多吗

不会,hashmap大小会自动调节成为2的幂

static final int tableSizeFor(int cap) {
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
/*
numberOfLeadingZeros求出int值的前n个0的个数
java内的int为32位
假设传入cap=13,cap-1=12 二进制即为1100 计算机内表示为28个0+1100
那么numberOfLeadingZeros值即为28
-1在计算机内补码表示为32个1
>>>表示无符号右移,就是把-1的二进制带上符号位右移
那么-1右移28位结果就是1111=15,结果返回15+1=16
*/

5.为什么长度到达8时变为红黑树

源码内有解读,hashmap在良好的hash算法条件下,值分布时符合泊松分布的,遇到单个桶内元素超过8的概率很小(一个桶内出现8的元素的概率为0.00000006)(为什么不会是数据太多导致单个桶内元素超过8呢?解答在下面),所以说遇到这种情况大多是因为hashcode函数写的很糟糕,一旦出现超过8的情况,估计这个桶还会增加更多的元素.hashmap不可能去改变用户的hashcode代码,只能使用红黑树对性能进行优化.

为什么不会是数据太多导致单个桶内元素超过8呢,因为装填因子为0.75,假设hashmap大小为n,若hashmap中有超过n*0.75个桶存了元素,就会扩容为原有大小的两倍.良好的hashcode函数不会让多个元素插入一个桶中.

6.为什么长度变为6时才重新变为链表

个人看法:提供一个缓冲区间,假设只有一个阈值8,现有一个桶有7个元素,若刚好不停的对这个桶进行增删增删增删,那么这个桶就会不停的在链表和红黑树之间切换,十分影响效率

7.为什么不直接使用红黑树

1.红黑树太占空间

源码中叙述如下

Because TreeNodes are about twice the size of regular nodes, we use them only when bins contain enough nodes 

2.在良好的hash算法条件下,桶元素超过8的概率很小,节点较少时,链表和红黑树查找速度相差不大

以桶元素为7举例,顺序查找ASL为(7+1)/2=4,

红黑树ASL为lg7<3

8.链表长度达到8就立刻变为红黑树吗

不是,源码如下

if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();

在转变成树之前,还会有一次判断,只有键值对数量大于 64 才会发生转换。这是为了避免在哈希表建立初期,多个键值对恰好被放入了同一个链表中而导致不必要的转化。

9.为什么装填因子是0.75

选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择

加载因子设置为0.75而不是1,是因为设置过大,桶中键值对碰撞的几率就会越大,同一个桶位置可能会存放好几个value值,这样就会增加搜索的时间,性能下降,

设置过小也不合适,如果是0.1,那么10个桶,threshold为1,你放两个键值对就要扩容,太浪费空间了

而且源码内有解读,hashmap在良好的hash算法条件下,值分布时符合泊松分布的,遇到单个桶内元素超过8的概率很小(一个桶内出现8的元素的概率为0.00000006)

10 为什么使用红黑树而不是AVL

理论上红黑树牺牲了一些查找性能 但其本身并不是完全平衡的二叉树。因此插入删除操作效率略高于AVL树.
AVL树用于自平衡的计算牺牲了插入删除性能,但是因为最多只有一层的高度差,查询效率会高一些.

posted @ 2020-03-15 00:31  INnoVation-V2  阅读(171)  评论(0编辑  收藏  举报