HashMap和ConcurrentHashMap

来源:互联网 发布:第九届网络攻防大赛 编辑:程序博客网 时间:2024/05/20 01:36

1.HashMap
(1)HashMap是一个存储Key-value键值对的集合,每一个键值对也叫作Entry,这些Entry分散储存在一个数组中,这个数组就是HashMap的主干
(2)HashMap数组每一个元素的初始值都是Null
(3)Put方法原理
hashMap.put(“apple”,0)
Index=Hash(“apple”):通过哈希函数确定Entry的插入位置(index),但是HashMap的长度有限,插入Entry过多,hash函数会出现index冲突,我们利用链表来解决
(4)HashMap解决哈希冲突问题:HashMap数组中每一个元素不止是一个Entry对象,也是一个链表的头结点,每一个Entry对象通过Next指针指向了它的下一个Entry节点,当新来的Entry映射到冲突的数组位置时,只需插入到对应的链表即可,
注意:新来的Entry节点插入链表,使用“头插法”,
(5)Get方法原理
①对输入的key做一次Hash映射,得到对应的index
Index=Hash(“apple”)
②同一位置匹配到多个Entry,顺着对应的链表的头结点往下找,直到找到我们要查找的key
③后插入的Entry被查找的可能性大,所以后插入的Entry放在头结点
(6)HashMap初始化默认长度是16原因
为了服务于从Key映射到index的Hash算法,Hash算法采用了位运算的方式,目的为了hash算法结果均匀
参考:http://mp.weixin.qq.com/s/HzRH9ZJYmidzW5jrMvEi4w
(7)ReHash(解决HashMap容量有限问题)
影响因素:
①Capacity :HashMap的当前长度,HashMap的长度是2的幂
②LoadFactor:HashMap的加载因子,默认值为0.75f
衡量HashMap是否进行Resize的条件如下:
HashMap.Size >= Capacity * LoadFactor
扩容步骤
①扩容:创建一个新的Entry空数组,长度是原数组的2倍
②ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组(因为Hash规则改变)
Hash公式:index = HashCode(Key) & (Length - 1)
(8)多线程下的ReHash
假设一个HashMap已经到了Resize的临界点(需要扩容)。此时有两个线程A和B,在同一时刻对HashMap进行Put操作:链表出现环形,当调用Get查找一个不存在的Key,而这个Key的Hash结果恰好等于3的时候,由于位置3带有环形链表,所以程序将会进入死循环!
2.ConcurrentHashMap
(1)为何不使用HashTable或者Collections.synchronizdMap
性能问题:无论读操作还是写操作,他们都会给整个集合加锁,导致同一时间的其他操作为之阻塞
(2)Segment:本身相当于一个HashMap对象,和HashMap一样,Segment包含一个HashEntry数组,数组中每一个HashEntry既是一个键值对,也是一个链表的头结点
Segment结构如下:
这里写图片描述
像这样的Segment对象,在ConcurrentHashMap集合中有多少个呢?有2的N次方个,共同保存在一个名为segments的数组当中。

ConcurrentHashMap结构如下:
这里写图片描述
可以说,ConcurrentHashMap是一个二级哈希表。在一个总的哈希表下面,有若干个子哈希表。
(3)ConcurrentHashMap的优势是采用了锁分段技术,每一个Segment好比一个自治区,读写操作高度自治,Segment之间互不影响
Case1:不同Segment的并发写入
这里写图片描述
Case2:同一Segment的一写一读
这里写图片描述
Case3:同一Segment的并发写入
这里写图片描述
(4)Get方法:
①为输入的Key做Hash运算,得到hash值。
②通过hash值,定位到对应的Segment对象
③再次通过hash值,定位到Segment当中数组的具体位置。
(5)Put方法
1.为输入的Key做Hash运算,得到hash值。
2.通过hash值,定位到对应的Segment对象
3.获取可重入锁
4.再次通过hash值,定位到Segment当中数组的具体位置。
5.插入或覆盖HashEntry对象。
6.释放锁。
(6)Size方法的统计
①遍历所有的Segment。
②把Segment的元素数量累加起来。
③把Segment的修改次数累加起来。
④判断所有Segment的总修改次数是否大于上一次的总修改次数。如果大于,说明统计过程中有修改,重新统计,尝试次数+1;如果不是。说明没有修改,统计结束。
⑤如果尝试次数超过阈值,则对每一个Segment加锁,再重新统计。
⑥再次判断所有Segment的总修改次数是否大于上一次的总修改次数。由于已经加锁,次数一定和上次相等。
⑦释放锁,统计结束。

原创粉丝点击