HashMap浅析(一)

HashMap浅析(一)

​ 无论几年的程序猿,面试必问的题,HashMap算一大山,这座山翻不过去,离自己梦想的公司想必是无缘的。

​ 很多人觉得会用HashMap不就行了吗,为什么非要研究它的原理呢?

​ 摸一摸自己的大光头,你真的会用吗?如何用效率高?除了当普通的数据结构使用,其他地方可不可以用到呢?

​ 接下来我会大致的对HashMap进行一个简短的分析。学习是一个循循渐进的过程,不要妄想一晚上学会什么原理,教你的是骗子,但你不能当傻子。

​ 第一节是对于数组容量为2的幂次方的解惑。后续会慢慢补充

初始容量

HashMap的初始容量默认为 1<<4,即16

//The default initial capacity - MUST be a power of two.
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

位运算讲解

我先给大家介绍一下代码片段中的位运算符,方便不懂的小伙伴知道如何计算

首先敲出来1的二进制,养成良好的习惯,32位记得写全


位移前: 0000 0000 0000 0000 0000 0000 0000 0001


代码中的<<是左移运算符,即左移四位.

这里需要注意,不是单指左移二进制中的尾部1


位移中:0000 | 0000 0000 0000 0000 0000 0000 0001


如上面所示:你可以把那个“|”当作一个水龙头,左移四位,就是让水龙头把离它近的四位0排出去


位移中:| 0000 0000 0000 0000 0000 0000 0001


排出去四位0之后呢,平白无故的少了四位兄弟,那得补充上来0对吧(空位补零)


位移后:0000 0000 0000 0000 0000 0000 0001 0000

位移前:0000 0000 0000 0000 0000 0000 0000 0001

尽管看上去像只是1左移了四位,但是并不能这么想,如果是:…0001 0011 0001呢。

上面只是说了基础的算法,那如果我想快速运算呢,不想通过二进制呢?

x<<y 相当于
x ∗ y 2 x*y^2 xy2
x>>y相当于
x / 2 y x/2^y x/2y

回归正题

注意代码上方的注释:默认初始容量-必须是2的幂次方值

那么为什么一定要让容量为2的幂次方值呢,带着这个问题继续看下去。

首先HashMap的底层数据结构:数组+链表,当链表长度大于8时,转变为红黑树;这是常见的答案。

数组是什么数组?链表是什么链表?(皮一下很开心)

//这一行代码应该都能了解吧
transient Node<K,V>[] table;

众所周知啊,HashMap中的数组使用散列即常说的哈希算法确定角标,使用链地址法解决哈希冲突。

//jdk1.8之后的hash算法实现,1.7的indexFor已经去掉了,但是用法没变。
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这里着重解释一下(h = key.hashCode()) ^ (h >>> 16)

是不是看着很懵?懵就对啦

下面举个例子:比如计算key为“程序猿”的值,hashcode值:30804443

在这里插入图片描述

代码中“>>>”是无符号右移,即逻辑右移,按照我上面位运算的讲解,这个时候是需要整体向右移动16位,

即图中黑竖线的右边数字全部舍弃掉。

在这里插入图片描述

然后老规矩,空出来的位置补0

在这里插入图片描述

java中的^运算是异或,相同为0,不同为1的运算

在这里插入图片描述

可以看到通过这样的运算,高16位其实没变化。

说的直白点,这么多步的运算,其实就是为了让key的hashCode值进行一个自身的高16位与低16位的异或运算,可以有效减少hash碰撞。

到这里其实只是算出来了HashMap中的hash值,接下来还有一个最常问到的代码片段

(n - 1) & hash

n是数组长度,hash是上面hash()方法算出来的hash值。

这里可以试着想一想,n是2的幂次方,也就是2,4,8,16等等等,他们的二进制有什么特点?

在这里插入图片描述

额,看起来并不明显是吧,接下来再看看n-1的呢

在这里插入图片描述

低位都是1,对吧,接下来结合起来看吧!

首先解释下&运算是按位与,即0&0=0,0&1=0,1&0=0,1&1=1.

那么想一想上面计算得出的hash值,两个进行按位与计算会是什么样的呢?

以n=16为例

在这里插入图片描述

看出来了吗,最终结果只跟hash值的最后几位有关系!!!

长度采取2的幂次方的原因

首先假设现在数组长度非2的幂次方,比如n=10,(n-1)=9,它的二进制为:1001,

现在有一个key=“程序猿”,通过上述的hash()方法计算出来的二进制的最后四位:1101,

1001&1101=1001

1001即得到的index值,但是换个思路想想,在保证n-1值(1001)不变的情况下,是不是还有另外的值会得到1001?当然是有的:1011.

而且还存在某些index值永远不会用到,比如0111永远不会出现。

结论:如果在不规定n为2的幂次方情况下,很容易出现数组下角标index重复(出现重复的概率提升)或者存在某些index永远用不到的情况,所以在n为2的幂次方时,只要保证hash()算法本身分布均匀就足够了。
还有一个重要原因:在n为2的幂次方情况下,hash%n等价于(n-1)&hash,而且"&“运算要比”%"快得多。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值