HashMap实现原理总结

接上一篇HashMap源码解析

HashMap实现原理总结

谈谈你对HashMap的理解?

先从底层数据出发。HashMap实现Map接口,存储键值对。JDK8版本底层结构基于数组+链表/红黑树,当每个桶中元素达到树化阈值8,并且数组总量大于64,由链表转为红黑树;元素小于阈值6,转回链表。针对JDK7版本做了优化,将最坏查询由O(n)降到O(logn)。

其次,HashMap线程不安全。

HashMap的数据插入原理是怎样的?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OyTt15wJ-1647533413146)(image/image-20220316070447609.png)]

HashMap怎么设定初始容量大小?

一般如果new HashMap()不传值,默认大小是16,负载因子是0.75。如果自己传入初始大小k,初始化大小为大于k的2的整数次方,例如如果传10,大小为16。

HashMap的哈希函数设计是怎样的?

参考上面hash实现

HashMap底层数组为什么总是2的n次方?

参考上面,主要是减少hash碰撞。

1.8还有别的优化吗?

1、链表的插入方式从头插法改成了尾插法,简单说就是插入时,如果数组位置上已经有元素,1.7将新元素放到数组中,原始节点作为新节点的后继节点,1.8遍历链表将元素放置到链表的最后;

在jdk1.7中,由于扩容时使用头插法,在并发时可能会形成环状列表,导致死循环,在jdk1.8中改为尾插法,可以避免这种问题,但是依然避免不了节点丢失的问题。

2、扩容的时候1.7需要对原数组中的元素进行重新hash定位在新数组的位置,1.8采用更简单的判断逻辑,位置不变或索引+旧容量大小;

将每个桶的元素分别处理成两个高低位链表。

3、在插入时,1.7先判断是否需要扩容,再插入,1.8先进行插入,插入完成再判断是否需要扩容;

扩容的时候为什么1.8不用重新hash就可以直接定位原节点在新数据的位置呢?

为啥扩容两倍?原因就在此,为了不用重新hash。

【分析】假设一个元素经过hash()运算得到值是hash,其在数组的桶是(oldCap-1)&hash位置。

假设oldCap=8,hash=9。

0111 & 1001 = 0001 = 1,元素在1位置。1000 & 1001 = 8 > 0

此时扩容两倍,cap=16

11111 & 1001 = 01001 = 9,元素在9位置。相当于 1 + 8。

上面的例子,如果hash=33,则resize前后元素都在1位置。

https://blog.csdn.net/u010425839/article/details/106620440/

HashMap并发时造成死循环问题解析

死循环问题在JDK 1.8 之前是存在的,JDK 1.8 通过增加loHead和loTail进行了修复。在JDK 1.7及之前 HashMap在并发情况下导致循环问题,致使服务器cpu飙升至100%。

HashMap在JDK1.7的扩容操作,重新定位每个桶的下标,并采用头插法将元素迁移到新数组中。头插法会将链表的顺序翻转,这也是形成死循环的关键点。

for (Entry<K,V> e : table) { // 
	while(null != e) {
	   Entry<K,V> next = e.next;
	   e.next = newTable[i]; // 关键几行代码
	   newTable[i] = e;
	   e = next;
	}
}

https://cloud.tencent.com/developer/article/1498035?from=15425

HashMap是线程安全的吗?

HashMap线程不安全。例如并发put操作就会存在数据覆盖。

怎么解决这个线程不安全的问题?

Java中有HashTable、Collections.synchronizedMap、以及ConcurrentHashMap可以实现线程安全的Map。

HashTable,是直接在操作方法上加synchronized关键字,锁住整个数组,粒度比较大;

Collections.synchronizedMap:是使用Collections:集合工具的内部类,通过传入Map封装出一个SynchronizedMap对象,内部定义了一个对象锁,方法内通过对象锁实现;

ConcurrentHashMap使用分段锁,降低了锁粒度,让并发度大大提高。

1.7与1.8的ConcurrentHashMap:实现有什么不同吗?

1.7 ReentrantLock+Segment+HashEntry实现,分段(segment)加锁。两次hash操作,先找段,再找到数据位置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SuMY8geN-1647533413147)(image/image-20220317000140508.png)]

JDK1.8之后ConcurrentHashMap.取消了Segment:分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。非常接近HashMap。

当竞争同一个分段锁概率小时,反而造成更新等操作长时间等待。

讲讲CAS是怎么保正线程安全的?

CAS(比较与交换,Compare and swap)是一种无锁算法。

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

CAS是基于系统命令的原子实现,只会被一个线程更新成功。因为非阻塞,所以没有死锁;没有线程阻塞和上下文切换带来的开销。

CAS会引入ABA问题

假设有一个变量A,修改为B,然后又修改为了A,实际已经修改过了,但CAS可能无法感知,造成了不合理的值修改操作。

AtomicStampedReference,设置版本号。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值