多角度彻底理解HashMap

一、从数据结构的角度看

在jdk1.7的时候hashmap是由数组加+链表实现的。jdk1.8的时候底层是由数组+链表+红黑树实现的

因为,数组+链表的话如果链表过长的话,这个hashmap的查找时间复杂度就是O(n)级别。时间复杂度太高,hashmap的效率就很低。所以在jdk1.8的是时候就引入的红黑树的数据结构。

但是jdk1.8 哪怕引入的红黑树也没有说一开始解决hash冲突的时候就使用红黑树。因为红黑树是一个近似平衡的二叉树。为了保障他近似平衡他需要左旋右旋的操作。但是这个左旋右旋对计算开销比较大,所以说如果这个链上的结点数少的话,性价比就很低。

所以,jdk1.8 对于链表什么时候转换成红黑树是有要求的,当链表长度大于8并且数组长度大于64,才会链转红黑树。否则就会对数组扩容,根据性价比来的。红黑树节点大小约为链表节点的2倍,在节点太少时,红黑树的查找性能优势并不明显,付出2倍空间的代价作者觉得不值得。

当红黑树结点小于6的时候,将红黑树转成链表。中间数是为了过渡。防止链表和红黑树频繁转造成资源浪费,时空开销。

二、从主体数组的角度看

在new HashMap<>对象时,并没有给数组分配长度。只有在第一次put时才分配了Node<K,V>[] table 数组长度。默认的初始容量是1<<4,也就是16。并且每次扩容的结果都是2的整数幂。如果说指定的数组容量不是2的整数次幂也会通过位运算计算出最接近这个数的2的整数次幂。

当链表长度大于8也就是第九个结点要插入的时候,会请求调用链表转红黑树的方法。但是这个方法最开始的时候会检查,数组长度是否大于64。如果长度小于64,则转化失败,转红黑树的这个方法就会调用数组扩容的方法。还有就是当数组中的元素超过阈值,这个阈值是负载因子*当前数组长度 负载因子默认是0.75。也就是说默认初始的时候,数组长是16,当第13个结点插入进这个数组的时候就发生扩容。

并且每次数组扩容的结果都是2的整数次幂。扩容是为了让结点散列的更加均匀。理想中的情况就是所有的结点都存在数组中,而不是链表或者红黑树。

扩容之后的元素会通过再hash来找到在新数组正确的位置。新数组中的位置有两种情况,第一是“原数组索引位置”,第二是“原数组索引位置+原数组长度”

因为索引位置的计算是通过 hash值与(数组长度-1)做&(按位与运算,都为一时才为一,否则为0),因为数组长度每次扩容都是乘2。举个例子:长度为16和32的数组,他们的长度-1的二进制后面有效位是01111(四个有效位)和011111(五个有效位)。32长度的数组比16长度的数组高了一个有效位,所以当hash值与数组长度-1作按位与操作时,结果只会有两种结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9a300J8S-1649072009560)(C:\Users\kwg2001\AppData\Roaming\Typora\typora-user-images\image-20220402192331575.png)]

计算索引的方式:

整个过程本质上有三步:

  1. 拿到key的的hashCode值
  2. 因为数组让高位的hashCode值也参与运算,重新计算hash值。hash计算:h ^ (h >>> 16),(按位异或、两个二进制位不同是才为1,否则为0)。**原因:**将高位也参与计算,目的是为了降低 hash 冲突的概率。如果没有这个过程的话索引值只取决于低位hashCode,容易出先hash冲突。
  3. 将计算出来的hash值与(table.length-1)进行&运算。

三、线程安全的角度看

  • 数据覆盖 jdk18
    • 当两个线程同时put数据时,经过计算得到的hash值相同,存在同一个位置,如果这两个线程都执行到判断出这个桶位置是null,但是其中线程一时间片用完了,另一个线程二完整的将数据放到了这个位置。当线程一再次执行时,没有再判断这个位置是否有数据,就直接将这个位置覆盖了。造成线程二的数据被覆盖。
  • 数据丢失 1.7 (画个图将数据丢失和成环死循环一块解释了) 头插法会产生数据丢失和死循环
  • 死循环 1.7
    • 死循环指的是jdk1.7的时候链表的插入采用的是头插法,当两个线程判断数组同时进行扩容时,会产生死循环。细节:(转移节点采用两个指针e,next)当线程一执行完next=e.next后时间片用完里。线程二在他的时间片内完成了扩容。当线程一再次执行时就会产生环,如果有get方法访问这个环,就出不去了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值