Hashmap的底层实现(以JDK1.8为例)

Hashmap是我们常用的一个数据结构。我们今天来剖析一下hashmap底层是怎么样的?由于hashmap的版本经常更迭,我们今天就以JDK1.8为例来说。

**hashmap其实是一个entry数组,**一个entry中包括一个key value 。而entry的存储参考数组的方式。
你可以把hashmap看做一个大号的数组,只不过里面存放的是一个个的entry。
hashmap的entry存放并不是根据key来存放的,而是hash值。当一个数据put进来的时候,hashmap会计算他的hash值,再根据hash值来决定存放的位置。
这样就会产生一个问题就是:如果两个entry的hash值是相同的,这会挤到一个位置上去。这就是我们讲的hash冲突问题。
hashmap的JDK1.7采用的是链表的形式,也就是hash冲突的地方采用链表相连接。
参考下图形式
在这里插入图片描述
但是这样暴露出一个问题:因为链表的查询效率是很低的,因为链表长度较长的时候,查询是相当拉胯的。
正由于这一点,JDK1.8 在这个进行了优化

在hash冲突的时候,不是单一的采用链表,而是增加了红黑树,
采用链表+红黑树相结合的方法。

因为红黑树的查询效率是非常高的。

个数小于等于6时,采用链表存储
个数大于8时,采用红黑树存储。

可以参考下图在这里插入图片描述

你可能会问,为什么阈值不相同呢?直接大于6红黑树,小于6链表存储多好啊?
其实你想的只是第一层,而作者早已在大气层了。
我们想象一个场景:一个hashmap经常的增删元素,恰好这个hash冲突的地方一会大于6,一会儿小于6,会发生什么?
一会变成链表;一会儿又变成红黑树。这会极其损耗性能的。
因为树化和反树化都会消耗cpu资源

因此作者设置了一个缓冲地带, 6 和8两个阈值
防止频繁切换数据结构,损耗性能。

hashmap的扩容机制?

hashmap每次扩容是自身的2倍
当自身的元素个数大于阈值,就会发生扩容

** 阈值(12)=容量(默认是16)乘以装填因子(默认是0.75)**
但是要注意:当扩容后,为了让哈希散列更加均匀,要进行部分的数据迁移。
这里JDK1.8做了优化,没有必要全部移走元素,只需要检验最高位是0还是1
如果是0,那么没必要动,如果是1,才需要动。
这个有兴趣的同学可以详细看下为什么这么可以做?

还有hashmap新创建时,执行put()函数会触发第一次扩容
当以后的put()不会,只会判断是不是已经达到阈值。

hashmap是线程安全的吗?
hashmap是线程不安全的,需要安全的hashmap就需要concurrenthashmap

这个JDK1.7是采用了分段锁,而JDK1.8采用的是cas行锁
缩小了上锁的范围,提高了并发的效能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你们卷的我睡不着QAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值