HashMap实现原理·扩容中两倍机制

HashMap实现原理之扩容两倍机制

前言

上一期【HashMap实现原理·二】中简单说了一下hashmap中取元素,相信大家对hashmap的put和get应该有所了解,那么本章讲讲hashmap扩容的知识。

回顾

首先回顾一下hashmap的结构图
在这里插入图片描述

扩容前后hashmap中数组对比

扩容后的数组下标图
在这里插入图片描述
扩容源码中的部分,可在【HashMap实现原理·一】中的扩容方法(resize)中查阅
在这里插入图片描述

源码分析

 	/**
 	 * hashmap中计算key的hash值的方法
     * Computes key.hashCode() and spreads (XORs) higher bits of hash
     * to lower.  Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     */
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

我们可以在前面第一篇文章中看到,hashmap的put方法中有下面这一段,重点是:(n - 1) & hash在这里插入图片描述

(n - 1) & hash 有什么特殊的作用?为什么hashmap容量是2的幂数并且扩容也是2倍增长?

在hashmap的put方法中,(n - 1) & hash 用来计算key的hash值对应在数组中的位置,n是数组的长度,hash是key 的hash值,我们来进行如下运算:
假设:n = 16,对应的二进制是 10000,那么n-1的二进制是 1111
例1:hash = 6,对应的二进制 110,(n - 1) & hash = 6
例2:hash = 7,对应的二进制 111,(n - 1) & hash = 7
例3:hash = 8,对应的二进制 1000,(n - 1) & hash = 8
例4:hash = 9,对应的二进制 1001,(n - 1) & hash = 9
例5:hash = 10,对应的二进制 1010,(n - 1) & hash = 10
例6:hash = 11,对应的二进制 1011,(n - 1) & hash = 11
例7:hash = 12,对应的二进制 1100,(n - 1) & hash = 12

假设:n = 10,对应的二进制是 1010,那么n-1的二进制是 1001
例1:hash = 6,对应的二进制 110,(n - 1) & hash = 0
例2:hash = 7,对应的二进制 111,(n - 1) & hash = 1
例3:hash = 8,对应的二进制 1000,(n - 1) & hash = 8
例4:hash = 9,对应的二进制 1001,(n - 1) & hash = 9
例5:hash = 10,对应的二进制 1010,(n - 1) & hash = 8
例6:hash = 11,对应的二进制 1011,(n - 1) & hash = 9
例7:hash = 12,对应的二进制 1100,(n - 1) & hash = 8
可以看到,取了7个例子,当n为16时,key在数组中的下标分布还是比较均匀的,而当n为10时,发生了比较明显的hash碰撞,我们知道hashmap是通过链表或红黑树存储数据的,当发生严重的hash碰撞,使得元素分布不均匀,导致数组的某几个位置上元素剧增,容易发生链表树化的过程即链表转红黑树。

当hashmap扩容为2倍后,需要重新计算元素的位置,扩容后hash碰撞也会减少,最坏的情况,hash碰撞不变,也就是不需要移动元素位置。当存在需要移动元素的位置时,也只需要在当前位置加上扩容前的容量即可。这点在hashmap扩容方法(resize) 中有所体现,如上图(扩容前后hashmap中数组对比)

举个例子:
假设扩容前:n = 16,对应的二进制是 10000,那么n-1的二进制是 1111
例1:hash = 14,对应的二进制 1110,(n - 1) & hash = 14
例2:hash = 15,对应的二进制 1111,(n - 1) & hash = 15
例3:hash = 16,对应的二进制 10000,(n - 1) & hash = 0
例4:hash = 17,对应的二进制 10001,(n - 1) & hash = 1
例5:hash = 18,对应的二进制 10010,(n - 1) & hash = 2
例6:hash = 19,对应的二进制 10011,(n - 1) & hash = 3
例7:hash = 20,对应的二进制 10100,(n - 1) & hash = 4
假设扩容后:n = 32,对应的二进制是 100000,那么n-1的二进制是 11111
例1:hash = 14,对应的二进制 1110,(n - 1) & hash = 14
例2:hash = 15,对应的二进制 1111,(n - 1) & hash = 15
例3:hash = 16,对应的二进制 10000,(n - 1) & hash = 16
例4:hash = 17,对应的二进制 10001,(n - 1) & hash = 17
例5:hash = 18,对应的二进制 10010,(n - 1) & hash = 18
例6:hash = 19,对应的二进制 10011,(n - 1) & hash = 19
例7:hash = 20,对应的二进制 10100,(n - 1) & hash = 20

&(与)运算分析:与运算的特性,相同取1,不同取0,从这个特性不难得出,任何正整数与若干个1(这里指二进制)与的结果等于本身(两个与数的位数相等)。

tip:当n为2的幂数,且仅当hash大于0时,(n - 1) & hash的结果等于hash%n(至于为什么hashmap用前者,可能是考虑到hash为负值的情况,也可能是计算机中位运算效率快,当然作者的想法也不一定对,欢迎大家一起探讨)

从上述可以得出以下结论:
  1. (n - 1) & hash用来计算key的hash值对应在数组中的位置
  2. 容量为2的幂数可以使元素分布更均匀,减少hash碰撞的概率,提升性能
  3. 由2可以知道,容量为2的幂数的好处,同时扩容为2倍还有一个好处,若需要移动元素位置,则在扩容前的元素下标加上扩容前的容量即得到扩容后的元素下标

结束语

本次文章简单介绍了一下hashmap的扩容特性、为什么初始容量是2的幂数和扩容两倍的好处。如内容有误,欢迎各路大神指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tiny丶bingo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值