学习笔记:hashmap+concurrentHashMap的底层挖掘

HashMap
它是JAVA中的一个集合框架通过key value的方式存储数据

最大容量为:1<<30 2的30次方

jdk1.8后他的底层是由数组+链表+红黑树组成的 (1.7之前只有数组+链表)

数组初始长度是0,第一次调用put时长度扩容为16,必须重写Key对象的HashCode和Equals方法。

数组大小:
当通过构造函数指定数组大小时,系统会自动将该值转化为一个>=他的2的N次方的数

原因:为了方便计算索引值hash&(length-1),防止出现数组越界,减少hash冲突,让数据散列的更均匀。

eg:通过构造函数指定数组长度为13,则系统转化为16,length-1为15(1111)可以方便的进行&运算。 (为了系统&操作的方便)

存储过程:
通过Key的hash(hash是由hashcode右移16位再异或(两个数不同为1)hashcode计算出来的)&(与,两个都为1时为1)数组长度-1来求出存储元素在数组中的索引

(采用hash&length-1的原因,直接用hashcode会造成大量的hash冲突,用hash则可以减少hash冲突)

若该索引没有元素则直接存放即可,若有元素,则比较hash,若hash相同,

(hash相同equlas不一定相同,hash不同equlas不同)则通过equlas方法来比较key,

若有相同的则覆盖, 若没有相同的则使用尾插法来存放到链表当中(jdk1.8之前是头插法,头插法会导致在扩容时出现两个节点的相互引用造成死链,1.8之后就是尾插法)。

hashmap的put方法:
一:判断是否是第一次调用put方法然后进行扩容

二:计算出所在位置判断是否存在元素,不存在直接存放

三:此时发生了Hash冲突,判断散列表长度是否达到阈值且发生了Hash冲突,则进行扩容 四:通过hash equals比较是否相同,相同则进行value的覆盖

五:判断是否是红黑树,如果是则插入树中

六:判断是否有链表,若有则依次比较,没有相同元素则尾插法插入

七:判断是否链表达到8,若散列表长度也达到了64则进行转换为红黑树,否则进行扩容

数据转换:
当链表长度达到8(根据泊松分布公式决定)且hashmap的数组长度大于64时(否则优先扩容)进行数据转化,将链表转为红黑树

当红黑树大小又变为6时,又回退到链表结构

红黑树所占节点大小约为链表结点的2倍,故一开始是链表结构。(当长度达到8时链表平均查找长度为4,时间复杂度O(n),红黑树平均查找长度为3,时间复杂度O(logn))

负载因子:
hashmap的默认负载因子(loadFactor)是0.75(泊松公式推导出来的)(决定多会儿扩容)为了让散列更均匀,负载因子越大(扩容越慢),空间利用率越高但发生Hash冲突的概率则越高,负载因子越小,发生Hash冲突的概率就低了, 但空间利用率低。(为了扩容机制减少Hash冲突还保证利用率)

扩容机制(resize):
数组已存储元素达到临界值(数组总长度*0.75)且这次添加数据发生了Hash冲突时扩容,大小左移一位变为原来的2倍。 每次扩容后都要rehash(重新计算Hash值,因为数组长度变了,索引位置也要改变)

hashMap的继承、实现关系:实现了serializable接口,cloneable接口 继承了AbstractMap抽象类

对象作为key时必须重写hashcode和equlas吗?
是的,必须重写,自定义对象我们为了让其语义上相等,eg:年龄姓名相同的就认为是一个对象,我们就需要重写equals,equlas相等则hashcode必然相同,所以要重写hashcode(hashcode相同,equlas不一定相同) 在put,get时,也需要hashcode来找到索引位置,equlas来比较对象是否相同。

hashmap在并发会产生的问题:
1.7时,在扩容时多线程进行访问会造成死锁,形成一个环(头插法,1.8就解决了) 并发put时会发生数据覆盖导致数据丢失,put后get拿不到数据

concurrentHashMap
jdk1.7:
采用的是segment+HashEntry的方式实现的 ,每个segment对象守护着整个散列映射表中的若干桶,每个桶是由若干HashEntry对象连接起来的链表。 segment是分段锁,多个线程访问时可以同时访问不同的segment从而提高并发效率,默认支持的最大并发量是16(因为最多有16个segment)。 segment继承自lock锁,所以保证了每个segment都是线程安全的,因为多个线程访问多个segment所以也就实现了全局的线程安全。(底层也是segment数组+链表)

jdk1.8:
对concurrentHashMap对象进行了重构,放弃了segment对象,由Node+synchronized+cas自旋锁组成(底层也是数组+链表+红黑树)

concurrentHashMap为什么取代了HashTable:
HashTable是在每个方法上都加锁,效率低下,concureentHashMap可以实现分段锁,且它在读时不加锁

concurrentHashMap和CopyOnWriteArrayList和CopyOnWriteArraySet都是线程安全的,且它们还是读写分离的,读时不加锁,写时才加锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

guojunjiang12345

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

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

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

打赏作者

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

抵扣说明:

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

余额充值