hashmap扩容_java集合(一)HashMap

哈希

Java8中HashMap的hash()方法:

static 

可以看到hash方法返回一个int类型的散列值。这个散列值与对象初始hashcode相比: 高16位不变 低16位与高16位做异或运算,得到新的低16位 这样得到一个32位的int类型新散列值。为什么不直接使用key的hashcode的呢?

在HashMap的putVal方法中,可以看到插入值的index算法:

if 

也就是说实际上的插入地址为(n-1)&hash,这里的n为哈希表长度。Hashmap习惯将表的大小设为2的幂,这样n-1相当于一个低位掩码,(n-1)&hash 的结果实际上就是hash的低位。因为n-1是低位掩码,所以(n-1)&hash的结果总是小于n,用&运算来代替%运算,效率提高了好几倍。这也是为什么我们在使用HashMap时,其长度应该取2的整次幂。

但是只取后几位的话,无疑会提高碰撞率。再回过头来看hash()方法,这样产生的散列值打乱了参与运算的低16位,此时的低位混合了原来的高位和低位,加大了低位的随机性,降低了哈希冲突的概率。

查找

JDK1.8中引入了红黑树来提升链表的查询效率。链表查找的平均时间为复杂度O(n), 红黑树则为O(logn)。为链表的长度超过8以后,红黑树的查找速度比链表高。所以链表长度超过8后,HashMap会将链表转化为红黑树。当然小于6时,HashMap也会将红黑树转换为链表。

扩容

JDK1.8中扩容不需要再rehash,转而使用了一种很巧妙的方法。扩容数组的长度是两倍的关系。比如大小为4,那么扩容大小就会变成8。也就是0100变成1000,那么n-1由0011变为0111。根据上面讲过的(n-1)&hash算法,也就是说,扩容后key的新地址,实际上就是hash方法算出来的值多取了一个高位bit。如果高位是0,那么索引不变。如果高位是1,索引就变成原来索引加上原来的表大小。下图是hashmap大小由16扩充为32的示意图,来自美团点评技术博客。

1ecd5ec2c3a4e1a35c8e042e550adcf8.png

这个设计非常巧妙,省去了重新计算哈希的时间。同时由于新增的0bit和1bit可以认为是随机的,这样扩容后就把之前的节点均匀的散列到哈希表中。扩容后,节点链表的前后关系不会变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值