HashMap源码剖析。

前言

声明,本文用的是jdk1.8。

一、HashMap剖析
首先看看HashMap的顶部注释说了些什么:
在这里插入图片描述
再来看看HashMap的类继承图:
在这里插入图片描述
下面我们来看一下HashMap的属性:
在这里插入图片描述
成员属性有这么几个:
在这里插入图片描述
再来看一下hashMap的一个内部类Node:
在这里插入图片描述
我们知道Hash的底层是散列表,而在Java中散列表的实现是通过数组+链表的~

再来简单看看put方法就可以印证我们的说法了:数组+链表–>散列表
在这里插入图片描述
我们可以简单总结出HashMap:

  • 无序,允许为null,非同步
  • 底层由散列表(哈希表)实现
  • 初始容量和装载因子对HashMap影响挺大的,设置小了不好,设置大了也不好

1.1HashMap构造方法

HashMap的构造方法有4个:
在这里插入图片描述
在这里插入图片描述
在上面的构造方法最后一行,我们会发现调用了tableSizeFor(),我们进去看看:
在这里插入图片描述
解释如下:
先来分析有关n位操作部分:先来假设n的二进制位01XXX…XXX。接着
对n右移1位:001XX…XXX,再为或:011XX.XXX
对n右移2为:00011…xxx,再位或:01111…xxx
此时前面已经有四个1了,再右移4位且位或可得8个1
同理,有8个1,右移8位肯定会让后八位也为1。
综上可得,该算法让最高位的1后面的位全变为1。
最后再让结果n+1,即得到了2的整数次幂的值了。
现在回来看看第一条语句:

int n = cap-1;

让cap-1再赋值给n的目的是另找到的目标值大于或等于原值。例如二进制1000,十进制数值为8。如果不对它减1而直接操作,将得到答案10000,即16。显然不是结果。减1后二进制为111,再进行操作则会得到原来的数值1000,即8。

看完上面可能会感到奇怪的是:为啥是将2的整数幂的数赋给threshold?

  • threshold这个成员变量是阈值,决定了是否要将散列表再散列。它的值应该是:capacity * load factor才对的。
    其实这里仅仅是一个初始化,当创建哈希表的时候,它会重新赋值的:
    在这里插入图片描述
    至于别的构造方法都差不多,这里我就不细讲了:
    在这里插入图片描述

1.2put方法
put方法可以说是HashMap的核心,我们来看看:
在这里插入图片描述
我们来看看它是怎么计算哈希值的:
在这里插入图片描述
为什么要这样干呢??我们一般来说直接将key作为哈希值不就好了吗,做异或运算是干嘛用的呢??
我们看下来:
在这里插入图片描述
我们是根据key的哈希值来保存在散列表中的,我们表默认的初始容量是16,要放到散列表中,就是0-15的位置上。也就是tab[i=(n-1)&hash]。可以发现的是:在做&运算的时候,仅仅是后4位有效~那如果我们key的哈希值高位变化很大,地位变化很小。直接拿来去做&运算,这就会导致计算出来的Hash值相同的很多。

而设计者将key的哈希值的高位也做了运算(与高16位做异或运算,使得在做&运算时,此时的低位实际上是高位与低位的结合),这就增加了随机性,减少了碰撞冲突的可能性。

下面我们再来看看流程是怎么样的:
在这里插入图片描述
接下来我们看看resize()方法,在初始化的时候要调用这个方法,当散列表元素大于capacity * load factor的时候也是调用resize()
在这里插入图片描述

1.3get方法

在这里插入图片描述
接下来我们看看getNode()是怎么实现的:
在这里插入图片描述

1.4remove方法

在这里插入图片描述
再来看看removeNode()的实现:
在这里插入图片描述

二、HashMap与Hashtable对比

从存储结构和实现来讲基本上都是相同的。它和HashMap的最大的不同是它是线程安全的,另外它不允许key和value为null。Hashtable是个过时的集合类,不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换
在这里插入图片描述

总结

在JDK8中HashMap的底层是:数组+链表(散列表)+红黑树

在散列表中有装载因子这么一个属性,当装载因子*初始容量小于散列表元素时,该散列表会再散列,扩容2倍!
装载因子的默认值是0.75,无论是初始大了还是初始小了对我们HashMap的性能都不好

  • 装载因子初始值大了,可以减少散列表再散列(扩容的次数),但同时会导致散列冲突的可能性变大(散列冲突也是耗性能的一个操作,要得操作链表(红黑树)!
  • 装载因子初始值小了,可以减小散列冲突的可能性,但同时扩容的次数可能就会变多!
    初始容量的默认值是16,它也一样,无论初始大了还是小了,对我们的HashMap都是有影响的:
  • 初始容量过大,那么遍历时我们的速度就会受影响~
  • 初始容量过小,散列表再散列(扩容的次数)可能就变得多,扩容也是一件非常耗费性能的一件事~
    从源码上我们可以发现:HashMap并不是直接拿key的哈希值来用的,它会将key的哈希值的高16位进行异或操作,使得我们将元素放入哈希表的时候增加了一定的随机性

还要值得注意的是:并不是桶子上有8位元素的时候它就能变成红黑树,它得同时满足我们的散列表容量大于64才行的~

在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值