「这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战」
HashMap
- 数据结构
数组 + 链表 + 红黑树(jdk>=1.8)
- 重要的变量
1.8版本
1 | java复制代码 /** |
- HashMap PUT
4.1 步骤
- 判断当前table也就是数组是否为空,为空则进行初始化,扩容也就是调用resize方法
- 根据计算出来的hash值 i = ((n - 1) & hash索引,判断当前table[ i ] 是不是为空,为空直接插入
- 接下来就是桶内操作了(一条列表、一颗红黑树或称为桶),定义一个节点e 作为工具人,暂存找到相同hash的节点。
- 获取当前数组table[i] 的节点 p,首先判断是否等于putKey的hash,相等将p节点赋予e节点
- 首节点hash 与 key 的hash 不相等,然后判断其是不是红黑树节点,是就进去遍历树节点,有就返回给e节点,没就先创建新节点返回传给 e 节点。
- 不是树节点,那就只能是链表结构了,遍历链表,寻找节点,也是有就返回给e节点,没就先创建新节点返回传给 e 节点,这里有一个判断,其链表长度是否到了可以转为树的判断,够长了就转红黑树。
- 随后,判断e节点工具人是否为空,不为空赋值操作,覆盖掉记录。
- 判断容量是否需要扩容。
4.2 hash值的计算
1 | java复制代码//计算hash值 |
4.2 (n - 1) & hash
1 | python复制代码 对于(n - 1) & hash 的简单理解 |
4.2 看看put的代码
1 | java复制代码//put代码 |
- HashMap 扩容
5.1 Jdk7-扩容死锁
jdk7链表采用头插法
而扩容的方式都是大同小异,都是通过新建一个大一点的数组(通常是2的指数幂,也必须是这样),随后将节点按照位置再安放好。
JDK7 HashMap 扩容在多线程场景下,会形成一个链表环的情况,也就意味着这是一个死循环,况且本来HashMap也不支持多线程的场景
JDK7 扩容代码
1 | java复制代码void transfer(Entry[] newTable, boolean rehash) { |
- 第一行:记录oldhash表中e.next
- 第二行:rehash计算出数组的位置(hash表中桶的位置)
- 第三行:e要插入链表的头部,所以要先将e.next指向new hash表中的第一个元素
- 第四行:将e放入到new hash表的头部
- 第五行:转移e节点,继续循环下去
5.2 JDK8 扩容
Java8 HashMap扩容跳过了Jdk7扩容的坑,对源码进行了优化, 采用高低位拆分转移方式,避免了链表环的产生
JDK8 扩容代码
1 | java复制代码//扩容的代码 |
—————————————————分界线————————
以及用于并发的ConcurrentHashMap
jdk7 用大hash表 套小hash 表 ,基于ReentranLock实现分段锁
jdk8 采用 synchronized + cas 保证线程安全
本文转载自: 掘金