HashMap实现原理
HashMap的put方法的具体流程
HashMap寻址算法
HashMap扩容机制
为何HashMap的数组长度一定是2的次幂
HashMap在1.7情况的多线程死循环问题
HashTable与HashMap
HashMap实现原理
- 数据结构:底层使用hash表数据结构,数组链表或红黑树
- 添加数据时,计算key的值确定元素在数组的下标
- key相同则替换 不同则存入链表或红黑树
- 获取数据通过key的hash计算数组下标获取元素
jdk1.7和jdk1.8的实现有什么区别?
JDK1.8之前采用拉链法。数组加链表
JDK1.8之后采用数组+链表+红黑树:链表长度大于8且数组长度大于64则会从链表转化为红黑树
HashMap的put方法的具体流程
1.判断键值对数组table是否为空或为null,否则执行resize0进行扩容(初始化)
2.根据键值key计算hash值得到数组索
3.判断tableil==null,条件成立,直接新建节点添加
4.如果tableli]==null,不成立
4.1判断tablei]的首个元素是否和key一样,如果相同直接覆盖value4.2 判断tableli 是否为treeNode,即tableil 是否是红黑树,如果是红黑树,则直接在树中插入键值对4.3遍历table们,链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,遍历过程中若发现key已经存在直接覆盖value
5.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold(数组长度*0.75),如果超过,进行扩容
HashMap扩容机制
HashMap使用的是懒加载,构造完不进行put方法,HashMap不会初始化或扩容table
- 在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次每次扩容都是达到了扩容闯值(数组长度*0.75)
- 每次扩容的时候,都是扩容之前容量的2倍;
- 扩容之后,会新创建一个数,需要把老数组中的数据挪动到新的数组中
没有hash冲突的节点,则直接使用 e.hash & (newCap - 1)计算新数组的索引位置
如果是红黑树,走红黑树的添加
如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash & oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上
HashMap寻址方法
- 计算对象的 hashCode0)
- 再进行调用hash0方法进行二次哈希hashcode值右移16位再异或运算,让哈希分布更为均匀
- 最后(capacity - 1)& hash 得到索引
为什么HashMap数组长度一定为2的幂次方
- 计算索引时效率更高:如果是2的n次幂可以使用位与运算代替取模
- 扩容时重新计算索引效率更高: hash &odCap==0的元素留在原来位置,否则新位置 = 旧位置 + oldCap
多线程死循环问题
链表插入元素时,JDK7 使用头插法插入元素,但在多线程的环境下, 扩容时会造成环形链或数据丢失,出现死循环。
HashTable与HashMap
HashMap
底层由链表+数组+红黑树实现
可以存储null键和null值
线性不安全
初始容量为16,扩容每次都是2的n次幂
加载因子为0.75,当Map中元素总数超过Entry数组的0.75,触发扩容操作.
并发情况下,HashMap进行put操作会引起死循环,导致CPU利用率接近100%
HashMap是对Map接口的实现
HashTable
HashTable的底层也是由链表+数组+红黑树实现。
无论key还是value都不能为null
它是线性安全的,使用了synchronized关键字。
HashTable实现了Map接口和Dictionary抽象类
Hashtable初始容量为11
性能比较低