首页 > 其他 > 详细

HashMap工作原理总结

时间:2021-02-23 11:17:49      阅读:29      评论:0      收藏:0      [点我收藏+]

Java中HashMap底层实现原理分析(JDK1.8)

在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

hash值:指的是数组的下标;

Hash值的计算方法:
// ^异或:如果相对应位值相同,则结果为0,否则为1
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

并以此确定key的在数组中的位置,

// i 为数组下标,使用位运算&,执行效率更高,&如果对应位上都为1,值为一,否则为0
int i = (n - 1) & hash;

Hash碰撞:多个元素计算得出相同的hashCode,在put时出现冲突。HashMap是将无限的元素存放到有限的数组中,所以hash碰撞在所难免。

  发生了hash碰撞,将新元素添加到同一hash值的后面,这样就形成了链表,同一个链表上的Hash值时相同的,所以说数组存放的时链表,而当链表长度太长时,链表就转换为红黑树,提升效率。
源码中的数据域:加载因子默认0.75,如果填充比很大则说明空间利用很多,如果不扩容则会导致链表越来越长,查找效率会越来越低
  HashMap红黑树解决问题方式:如果某个位桶的记录过大时(默认链表长度为8时),HashMap会动态的使用一个专门的treemap实现来替换链表,同一位置的元素,key的hash值是不同的,小的放左子树,大的放右子数,这样做效率更高,时间复杂度O(logn),而不是糟糕的O(n);
  具体工作原理:使用Hash值作为树的分支变量,如果两个哈希值不等,但指向同一个桶的话,较大的那个会插入到右子树里,如果哈希值相等,HashMap希望key值最好实现了comparable接口的,这样他可以按照顺序进行插入。

常见问题问答

为什么HashMap的初始容量是16,扩容时一定要是2的n次方?

从上述我们可以得知,从key映射到HashMap数组的对应位置,会用到一个Hash函数,那么问题来了,如何实现一个尽量均匀分布的Hash函数呢?

取模运算?这种方式固然可以,但是效率很低,为了实现高效的Hash算法,HashMap的发明者采用了位运算的方式。

index = HashCode(key) & (length-1)

下面我们以值为“book”的Key来演示整个过程:

1.计算book的hashcode,结果为十进制的3029737,二进制的101110001110101110 1001。

2.假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。

3.把以上两个结果做与运算,101110001110101110 1001 & 1111 = 1001,十进制是9,所以 index=9。

可以说,Hash算法最终得到的index结果,完全取决于Key的Hashcode值的最后几位。

4.假设需要扩容,第一次扩容,扩容后HashMap长度变成32,计算Length-1的结果为31,二进制为11111;

5.将book的hashcode与数组长度做与运算,101110001110101110 1001 & 11111 = 1001,此时并不需要移动book在数组中的位置。index=9,

6.假设第二次扩容,101110001110101110 1001 & 111111 = 101001,此时index=32+9=41,仅需要移动到oldIndex + oldLength = 9 + 32 = 41;

这样可以保证hashMap中元素均匀分布。

如何处理hash冲突?

链地址法:数组中每个元素都存有一个单链表,发生 Hash 冲突时,只是将冲突的 value 当作新节点插入到链表(HashMap 解决冲突的办法)。

HashMap是使用链地址法解决hash冲突的,即数组+单链表+红黑树;

HashMap是头插法还是尾插法

1.JDK8以前是头插法,JDK8后是尾插法

2.为什么要从头插法改成尾插法?
A.因为头插法会造成死链,参考链接

 while(null != e) {
     Entry<K,V> next = e.next; //线程1执行到这里被调度挂起了
     e.next = newTable[i];
     newTable[i] = e;
     e = next;
 }

 多线程,进行rehash,transfer()方法时,造成死链。

比如:hashmap死锁主要是一个线程执行transfer过程中,被另一个线程改变了链表元素(共同资源)的指针指向
B.JDK7用头插是考虑到了一个所谓的热点数据的点(新插入的数据可能会更早用到),但这其实是个伪命题,因为JDK7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置(就是因为头插) 所以最后的结果 还是打乱了插入的顺序 所以总的来看支撑JDK7使用头插的这点原因也不足以支撑下去了 所以就干脆换成尾插 一举多得

 

 

 

 

HashMap工作原理总结

原文:https://www.cnblogs.com/little-learner/p/14414387.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!