HashMap的源码关键解读

HashMap的源码关键解读

一:整体结构

hashMap的源码有太多文章解读了,这里只是记录关键点。对很多不重要的东西略过了。

java8以后对于hashMap的整体做了优化,是基于一个数组+链表+红黑树的实现。结构图如下:

image-20220325001133653

二:key索引映射

  • 当我们往hashMap里面put数据的时候,会先获key在数组中的位置。但是hashMap为了让我们的数据在数组中更加分散,在取模前进行了散列hash。
 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

源码如上,

关键代码简化如下:

int h = key.hashCode();

h ^ (h >>> 16);

将key的hashcode 和 key的hashcode 右移16位值进行异或操作。这里有两个优化点

1:散列算法

直接用key的hashcode取模的话,只有hashcode的后面几位可以影响取模的结果,可能会导致数据分布不均,hash冲突严重。

所以采用将hashcode 的int值的32位分成16位的高位和16位的低位,然后将值右移16位。最终效果就是将高位和低位进行异或操作,让高位值参与到后面计算中。尽可能的将key再数组中打散,同时添加性能也好。正如源码注释说最具性价比的打散方式。

image-20220325225748464

ps:如果key的hashcode小于2的16次方(65536)那么这个散列算法没有作用的。

2:取模优化

将hashcode 散列后得到散列值,还需要确认我们key存储到table[]数组的什么位置,如果对散列值进行直接取模table[]数组的size 也是可以得到key存放到数组的什么位置的。但是源码采用的&的方法,可以达到和取模一样的效果,源码如下:

 (n - 1) & hash

这里的n = tab.length,这种方法必须数组的大小必须是2的倍数。

下面就是hashMap扩容时候获取数组大小的源码

    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

cap是扩容的容量值,然后减一得到n。后面五次移位然后再和原值或得操作,者五部主要得到扩容容量是2得倍数。

为什么一定是2得倍数呢?首先n是int值是32位得,然后第一位一定是1;

加入现在n=17 二进制就是10001 然后右移动一位,就是1000 然后和10001或之后得到11001,然后第二次操作得到11111。按照上面五部,移动就是32位。所有得值都是1了。然后加1 就一定是2得倍数了。

为啥要n = cap - 1 这里主要是扩容得cap本来就是2得倍数得时候,计算后的值会比原来大一倍。减去以后处理cap为2得倍数得时候,计算后值还是原值。

继续看这段代码: (n - 1) & hash。这段代码是hash&n一样得。

n-1 一定全部都是1,这个前面已经说了。假如n=16 那n-1 =15,二进制就是1111,假如hash=0111110110和1111执行与操作就是0110,也就是取了n-1映射得后面几位和取模一样得。但是比取模性能要搞。

这也就是为什么数组扩容一定是要2得倍数。

三:HashMap的扩容

1:获取扩容得大小

关于数组得扩容为啥是2得倍数,前面已经说明过了,这里就不再重复说明了。

2:将数组,链表,红黑树拷贝到新得位置。

数据拷贝,在源码里面就是resize 方法。数据拷贝需要重新对数据进行位置安排了。

  • 空数据

  • 数组数据

    重新映射拷贝到新表格

  • 链表数据

    低链基于当前索引放到存放新表格,也就是原来索引是4,新得索引位置还是4;

    高链放置当前索引+旧容量位置,原来索引是4,原来得容量是16,那么新得索引位置就是16+4=20;

    如何理解高链和低链呢?

    前面说过我们得计算索引得方法是,容量减一,然后与hash值。假如现在A得hash是111010,b得hash是101010,数组得容量是16,减一后的二级制就是1111。那么A,B原来得索引位置就是1010 =10 得位置。

    然后现在扩容一倍,就是32了,减去一二进制就是11111 如下图:

    image-20220326210912311

    分别与计算后结果是:11010,1010,换成二级制就是26,10。从结果知道B得记过没有变,我们定义为低链,A定义为高链。

    这个26 也可以用另外方法计算出来就是原来容量16,加上原来得索引10。

    上面讲得原理,下面看看源码是如何计算高链和低链得。

    (e.hash & oldCap) == 0
    

    node得hash值,原来数组得容量,等于0就是高链,否则就是低链。

  • 红黑树数据

    拆分树重新映射,扩容后冲突少了,可能重变成链表结构。

四:put的流程

略,

补充一点就是,链表树化得时候有两个条件1:链表长度超过8,2:就是数组大小大于等于64,小于得话先扩容。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值