一. 简介
- HashMap是底层采用
数组+链表
实现的线程不安全
的Key-Value
结构。
二. 原理
-
插入元素
通过put()方法传递键值对时,键先调用hashCode()方法,并返回一个hash值,通过hash值对数组长度取余(即index = HashCode(Key) % Array.length
)找到数组对应的位置,再遍历链表判断链表中是否有此Key,如果链表中已有此Key(即发生了哈希冲突,解决方法有:开放寻址法、链表法、再哈希法、建立公共溢出区),HashMap采用了链表法解决哈希冲突,则用新Value值代替旧Value值,若没有此Key则插入键值对到链表头部
(Java8之前)或链表尾部
(java8之后)。开放寻址法:寻找下一个空的散列地址; 链表法:头插法和尾插法; 再哈希法:有多个不同的Hash函数,使用第二个,第三个,….,等哈希函数计算地址,直到无冲突; 建立公共溢出区:将哈希表分为基本表和溢出表两部分,和基本表发生冲突的元素填到溢出表
-
初始化大小:16
-
扩容机制
(1)影响扩容的因素
Capacity:即HashMap的当前长度;
LoadFactor:即HashMap的负载因子,默认是0.75f。(2)衡量扩容的条件:
HashMap.Size >= Capacity x LoadFactor
(3)扩容步骤
扩容
:创建一个新的Entry空数组,长度是原来数组的2倍;
ReHash
:遍历原Entry数组,把所有的Entry重新Hash到新数组中,其目的就是将原来的Entry尽可能均匀的分配。 -
头插法和尾插法
「头插法」:Java8之前,采用头插法,即将键值对插入到链表头部。扩容时可能会改变元素在新数组中的位置,在并发情况下,可能会出现环形链表
。「尾插法」:Java8之前后,采用尾插法,即将键值对插入到链表尾部。扩容时不会改变元素在新数组中的位置,不会出现环形链表。
-
java8的改进
(1)Java8后,采用数组+链表+红黑树实现,链表大小大于等于8,且数组长度大于等于64时,会自动转为红黑树;当删除小于等于6时,重新变为链表;
(2)Java8之前,采用头插法;Java8之前后,采用尾插法。
三. HashMap封装为线程安全
- 可通过Collections.synchronizedMap()把一个HashMap封装为线程安全;
- 原理:SynchronizedMap内部维护了一个Map和互斥锁mutex。
四. HashMap和Hashtable的比较
- 同:二者的元素均是无序的,负载因子均是0.75;
- 异
HashMap | Hashtable | |
---|---|---|
初始化容量 | 16 | 11 |
扩容机制 | 2倍 | 2倍+1 |
父类 | AbstractMap | Dictionary |
迭代器 | Iterator | Enumerator |
快速/安全失败 | fail-fast(快速失败) | fail-safer(安全失败) |