HashMap部分源码浅析

      HashMap的底层是数组+链表(jdk1.7是数组+链表,之后数组+链表+红黑树) ,key唯一的,value可以重复,允许存储null 键null 值,元素无序。

      如果两个不同Key的元素,通过哈希函数计算出来的哈希值是一样的,就会产生哈希冲突,哈希冲突有很多种解决办法,开放地址法(发生冲突,继续寻找下一块未被占用的存储地址),拉链法,再造哈希法等等,但是HashMap使用的是拉链法。也就是在哈希冲突的地方创建一个链表。查询的时候,如果定位的地方没有链表,则直接取出,如果存在链表,则需要遍历链表,逐个比较key(利用equal方法)。而插入数据的时候也类似,如果定位到的位置不含有链表,直接放入数组指定位置,如果还有链表,存在即覆盖旧的value,否则在链表头插入;


Entry数组

HashMap的基本的数据结构是Entry数组,这个数组有四个元素:key,value,next指针,hash值。

 


1、get方法

大概的步骤为:首先key传入hash方法,这个方法里面后计算key的hashcode,然后又把hashcode进行一次再散列。根据再散列的值调用indexFor函数查找所在的Entry数组的索引号。

如果这个数组上有链表,遍历链表里面的每个元素,如果找到某个key和查找的key通过equal判断相等,返回这个数组元素。

 


indexFor函数:

      原本计算是应该用hash值对数组长度取模运算,计算出来下标,但是实际上是长度-1,然后与。因为数组长度一定为2的N次方,-1之后所有位都为1,再进行与运算这两个结果是一样的,但是与运算的效率显然要高多了。

       那为什么要保证length的长度为2的N次方??因为2^N-1所有位都是1,而如果不是这种情况,比如某一个位是0,这样不同的h来与的时候,这一位就始终都是0,就会增加哈希碰撞的概率,导致很多不同的哈希值都对应着同一个Entry数组索引。

具体来说比如从1111,变成1110,那么1111和1110和他“与”结果都是一样的。


hash()方法

 


2、put方法

         一开始一样的,把key传入一个hash()方法,内部调用hashcode()计算哈希值,然后对哈希值进行一次再散列。然后根据再散列值调用indexFor()来查询该值对应的Entry数组索引。然后遍历这个数组索引上面的 链表元素,如果找到相同的位置上已经有了元素,那么更新value,返回旧的value;而如果没有这个元素,那么把新的元素加到链表头,然后返回null。


3、扩容

        当 HashMap 中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的,所以为了提高查询的效率,就要对HashMap的数组进行扩容,在HashMap数组扩容之后,最消耗性能的点是:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是 resize。

       这里比较重要的就是负载因子,负载因子默认为0.75,也就是当当前占用的元素个数超过总容量的0.75时,就把数组大小翻倍。然后计算每个元素在新数组中的位置,再移动过去。

那为什么选择0.75呢?

如果太大,比如为1,那么数组满了才会扩充,这势必会导致出现较多的哈希冲突,也就是说链表会比较长,这就会降低添加和查询的效率(因为每次都要遍历链表)。而如果太小,就会没放多少数据就要扩充一倍,导致空间的浪费。


参考网站:

https://www.jianshu.com/p/4aa3bb16f36c

https://www.cnblogs.com/shoulinniao/p/11966194.html

https://www.cnblogs.com/dolphin0520/p/3681042.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值