彻底搞定HashMap、ConCurrentHashMap jdk1.7 or 1.8

hashmap面试必问。所以一定要弄明白

首先介绍hashmap

jdk1.7hashmap

底层采用   数据 + 链表 比较简单。相信很多人都自己实现过一遍。采用头插法插入数据。这种方式不安全

下图

重点来了:

当数组扩容的时候,我们采用的方法是这样的,扩容当然是在原有的基础 * 2 , 

这里暂时记下当前容量为 capacity 。两倍就相当于原来的capacity向左移动一位,但还是 111111这种形式的。

我们计算index 值是  key.hash  &  capacity - 1  ,所以我们只需要在key.hash 最新增加的那位吧,(什么叫做最新增加的那位,额,之前我们对应的length 二进制不是 1111吗,* 2 之后就会变成 11111,然后对应的key.hash不也就多加了一位吗)

如果这位为1,新的index = capacity + oldIndex , 如果为0 , index = oldIndex;

接下来我们要了解的是,为什么头插法不安全。

头插法会导致链表死循环。你还听不懂的话看图吧

 

原始链表:

 

容量翻倍 变成4,B这个时候算的是index = 2.

 

C的index也等于2    然后你看一下,是不是死循环了。

 

 

JDK1.8 hashMap

hashmap采用的是 数据 + 链表 + 红黑数。更加高

当链表上的元素超过8,就转成红黑树存储。

(h = k.hashCode()) ^ (h >>> 16)  //采用高16为异或低16位,这样尽量让所有的数据都参与到运算来。更加分散吧。

h & length - 1 // 下标

然后和jdk1.7是一样的找下标。

 重点: hashMap采用的是尾插法避免出现环链表。

 

 

jdk1.7  ConCurrentHashMap 

Segment + HashEntry + ReentrantLock

使用分段锁,segment[] 每一个数组坑位加一把锁。 也就是对Node数组添加volatile关键字。

Segment这个类继承了重入锁ReentrantLock

怎么扩容Segment这个类继承了重入锁ReentrantLock?

段(segment)+数组+链表。扩容操作是先遍历数组元素,在每个数组元素上遍历一遍链表,找到链表的最后n个结点(这n个结点在新数组一定属于同一个数组位置上),把这n个结点先挂接到新数组的数组位置上,这也叫lastRun机制。至于原数组的头结点到倒数n个结点之间的结点,再遍历一遍,通过复制每个结点的机制挂接到新数组上。

 

 

他到底是怎么扩容的 你能不能说清楚一点。认真研读下面文字。

 

它是指使用lastRun标识,标志那些在后面有一串都是0的结点,因为他们都是留在原位置的 lastRun标记位置开始到末尾,那个新增的数是0;当然扩容是换了一个长度* 2 的新数组进行操作。 直接就可以提到新数组中去。然后剩下的就是直接遍历了,一个一个来。莫得办法。

 

 

jdk1.8ConCurrentHashMap 

底层使用: 数组+链表+红黑树+数据加锁。也就是在1.8 hashmap上面进行操作。

锁的粒度更加细。

采用多线程去扩容。只是内部操作很多属性都是加了 volatile关键字

首先将扩容线程分成两半,每段线程独立操作,每一半里面又会分成两半。(条件:不能再分,线程达到达到最大)

 

每一个线程可以将链表中的数据拆分成两种

1、新增位为0 , 插入oldIndex

2、新增位为1,插入oldIndex + capacity

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值