HashMap实现原理

HashMap是平时开发经常用到的集合类,本文针对JDK1.7和1.8 HashMap源码来讲解其实现原理和区别。

一、数据结构
1、HashMap在1.7上的数据结构主要是用的:数组+链表,数组和链表节点的实现类是Entry类;
2、HashMap在1.8里面主要是:数组+链表/红黑树,当链表长度大于8时则转化为红黑树;数组和链表节点的实现类是Node类;
在这里插入图片描述
二、Hash值计算
扰动函数目的是减少Hash碰撞,大约10%左右。
1、HashMap 1.7用了多次扰动处理(4次位运算+5次异或运算)

static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

2、HashMap 1.8上只用了1次位运算+1次异或运算。

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这里的位运算将高位移到低位的原因:hash值的差异主要在高位,而数组寻址又主要在地位,那么这种处理可以有效的降低hash碰撞。

三、 链表数据插入方法
put过程:key通过hash计算后定位到数组下标i,如果数组i位置为null则直接将Entry放入即可,如果i位置部位null,则需要遍历i位置的链表,如果存在相同的key则更新值,如果key都不等则将新Entry插入链表:
1、HashMap 1.7使用的是头插法,扩容后位置与原链表位置相反。防止尾部遍历,不然每次插入的时候都要定位到尾部节点,如果多线程情况下容易形成环
2、HashMap 1.8使用的是尾插法,因为其中会使用到红黑树,构树红黑树有个过程,扩容后位置与原链表相同;
扩展:因为HashMap 1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在HashMap 1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。

四、内部Entry类
1、在HashMap 1.7中,HashMap中有个内置Entry类,它实现了Map.Entry接口;
2、在HashMap 1.8中,这个Entry类变成了Node类,也实现了Map.Entry接口,与HashMap 1.6中的Entry是等价的。

扩展:
1、HashMap的key和value都允许使null,key只允许一个为null,value允许多个为null,因此不能根据get返回为null判断是key不存在还是值为null,可根据containsKey来判断key是否存在。

2、HashMap初始值为16,扩容时newSize = 2*oldSize,size一定时2的n次幂 — 这样最后一位二进制数才一定是1,这样能最大程度减少hash碰撞(主要跟定位key所在的数组下边有关:hash & (length -1))。

3、扩容:当Map中的元素数大于等于极限负载因子(0.75) *容量时触发扩容。

if (++size > threshold)
            resize();

负载极限0.75是时间和空间的一种极限,如果负载极限变小,则新增元素触发扩容的概率增大,发生hash碰撞的概率降低,查询查询效率提高但增加了hash表内存的开销;如果负载极限设置变大,则hash碰撞的概率增大,则查询效率降低(get和put都要用到查询)。

4、HashMap 1.7是基于数组+单链表实现,为什么不用双链表哪?
当不同key通过hashCode计算相同时,则发生了hash冲突(碰撞),用链表是为了解决Hash冲突,单链表能实现为什么要用双链表呢?如果使用双链表的话还需要更大的存储空间,也就是说使用单链表就够了。

5、引入红黑树的原因?
引入红黑树的原因就是为了提高HashMap的性能,即解决发生哈希碰撞后,链表过长从而导致索引效率慢的问题(时间复杂度O(N)),具体做法是利用红黑树快速增删改查的特点,从而将时间复杂度从O(N)降低到O(logn);

6、为什么要用红黑树,而不用平衡二叉树?
红黑树的平衡度相比平衡二叉树要低,对于删除、插入数据之后重新构造树的开销要比平衡二叉树要低,查询效率比普通二叉树高。所以选择性能相对折中的红黑树。(扩展:平衡二叉树这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多。更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么平衡二叉树还是较优于红黑树。)

7、既然红黑树那么好,为啥HashMap不直接采用红黑树?
这是因为红黑树需要进行左旋、右旋操作,而单链表不需要。并且单拉链表的方式实现起来比较的简单。

8、为什么是当大于8个的时候才转换红黑树?
如果元素小于8个,查询成本高,新增成本低。如果元素大于8个,查询成本低,新增成本高。至于为什么选数字8,是实验数据的结果,就像loadFactor默认值0.75一样。理想情况下使用随机的哈希码,容器中节点分布在hash桶中的频率遵循泊松分布,按照泊松分布的计算公式计算出了桶中元素个数和概率的对照表,可以看到链表中元素个数为8时的概率已经非常小,再多的就更少了,所以原作者在选择链表元素个数时选择了8,是根据概率统计而选择的。

9、HashMap查找元素的时间复杂度我们一般都认为是O(1)?
关于这一点很多人会有疑问,他的底层实现是红黑树和链表,为什么说是O(1)哪,这是因为我们在使用HashMap的时候,一般都会按照预估的容量在初始化的时候,都会指定好容量,这样的话就尽量的避免了hash碰撞,没有碰撞的话,就不会出现构造链表和红黑树的情况,而hash一次的时间复杂度就是O(1)。对于绝大多数场景来说,我们都可以预估好HashMap的初始化容量,因此,我们认为是O(1)。

10、实践HashMap需要注意的地方:参考阿里巴巴开发手册
HashMap使用时
在这里插入图片描述

参考:
http://www.yuanrengu.com/index.php/20181106.html
http://www.yuanrengu.com/index.php/2017-01-17.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值