HashMap底层实现原理

HaspMap实现原理

我们先看一下从put方法入手来分析这个一下这个流程, 看下面流程图
在这里插入图片描述
我们看完这个图片后心中应该有个大概了 用与运算计算出数组下标—判断链表值是否相等如相等覆盖否则向下添加—如果链表大于8就转成黑数

到这里是不是觉得有点抽象,我在网上又找了几张图片可以让我们更加清楚的了解它的原理。

put(K, V)
在这里插入图片描述

put(K, V) 相同hash值
在这里插入图片描述
其实put和get和remove原理都是一样, 通过与运算计算下标然后判断是否是红黑树类型 如果不是: 根据单链表特性来获取和删除。如果是: 转换成红黑树判断进行查找和删除动作。

理解HashMap设计

1.如何计算链表数组下标

在这里插入图片描述
从上面源码中可以得到这套公式:(数组的大小 - 1) 与运算 hash值 = 下标。
那这套公式的作用是什么呢?
他可以帮助我们把数据尽量平均分配给每个下标,减少冲突减少查询时间。
这时候我们就想到为什么不用Key值的HashCode值与HashMap的长度进行取模运算,即 index = HashCode(Key) % hashMap.length这样也能平均分配。但是!这种取模方式运算固然简单,然而它的效率是很低的,浪费性能。所以jdk用了与运算的方式index = HashCode(Key) & (hashMap.length - 1)

2.如何降低冲突(加载因子为什么是0.75?)

在这里插入图片描述
从这里我们就可以看出来他判断扩容的依据是什么了, 容量 * 加载因子 = 判断resize的依据, 已知这套公式得出出下面这些结果。

  1. 日常初始化的时候尽量根据实际情况, 按照2的幂次方来设置初始值. 避免冲突降低多次扩容减少rehash次数
  2. 如果个加载因子过高虽然减少了rehash次数, 提高了空间利用率, 但是同样会导致冲突增大,增加查询实际成本。
  3. 如果个加载因子过低虽然减少了查询时间成本, 但是空间利用率很低,同时提高了rehash操作的次数。
  4. 为什么要选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择,

3.为什么初始容量要用2的次幂

举例:
“abc”十进制hashcode为96354,二进制为‭1 0111 1000 0110 0010‬,HashMap的默认长度为16,与15进的二进制1111位运算, 得到index = 2;
可以看出来,hash算法得到的index值完全取决与Key的HashCode的最后几位。这样做不但效果上等同于取模运算,而且大大提高了效率。

如果不是会怎样呢?
我们假设HaspMap的初始长度为10,要与9的二进制1001进行位运算。得到0,
但是 如果 hashCode为1 0111 1000 0110 1001 或者1 0111 1000 0110 1011这样会造成 有些索引出的值挤压太多。无法更好的分布均匀。

总结

  1. 往往我们系统申请内存的时候要2的倍数, 内存分页 往往4k
  2. 提高运算速度
  3. 避免内存碎片
  4. 增加散列度, 避免冲突

4.在什么情况下转成红黑树

在链表的长度超过了8,且下标的数量必须大于64如果小于64的时候只会扩容

总结

其实原理不是很难,底层的一些运算比较巧妙, 合理使用与运算和位运算有助于程序提高效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值