1. 基本说明
-
HashMap在jdk1.7为数组 + 链表结构,1.8后改为了数组 + 链表/红黑树
-
new HashMap<>()
不指定容量时,默认为16
-
HashMap的大小为
2的幂
,因为当数组大小为2的幂时,数组下标的计算公式(n - 1) & hash
效果等价于n % hash
,且更高效,所以HashMap限制大小为2的幂的同时,选用了(n - 1) & hash
作为计算公式 -
扩容因子默认为
0.75
,容量变化后,会自动计算扩容阈值threshold
,数据插入完成后会判断++size > threshold
,true
的话,即插入后容量大于阈值,执行扩容 -
红黑树长度 <= 6
时,红黑树转为链表;链表长度 >= 8
转化为红黑树// resize 扩容时触发 if (loHead != null) { // UNTREEIFY_THRESHOLD = 6 if (lc <= UNTREEIFY_THRESHOLD) tab[index] = loHead.untreeify(map); else { tab[index] = loHead; if (hiHead != null) // (else is already treeified) loHead.treeify(tab); } } if (hiHead != null) { if (hc <= UNTREEIFY_THRESHOLD) tab[index + bit] = hiHead.untreeify(map); else { tab[index + bit] = hiHead; if (loHead != null) hiHead.treeify(tab); } }
// 数据插入 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // 链表转红黑树判断 TREEIFY_THRESHOLD = 8 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; }
2. 不同类型对比
HashMap | HashTable(不推荐使用) | ConcurrentHashMap | |
---|---|---|---|
底层实现 | jdk1.7:数组+链表 jdk1.8:数组+链表/红黑树 | 数组+链表 | 数组 + 链表 + 红黑树 |
线程安全 | 线程不安全 | 线程安全,大部分方法被synchronized修饰,执行时会锁住整个HashTable | 线程安全 jdk1.7:分段锁 jdk1.8:CAS+Synchronized,只锁定当前链表或红黑二叉树的首节点 |
效率对比 | 高 | 低 | 中 |
key限制 | 最多只会有一个为null | 不允许为null | 不允许为null |
value限制 | 可以有多个为null | 不允许为null | 不允许为null |
扩容 | 默认容量16 newSize= oldSize * 2 | 默认容量11 newSize = oldSize * 2 + 1 |
3. 数据插入流程
这里以jdk1.8的来介绍,数据插入流程如下
- 判断hashMap的数组是否为空,为空进行初始化;
- 不为空,计算 key 的 hash 值,通过
(n - 1) & hash
计算应当存放在数组中的下标 index; - 查看 table[index] 是否存在数据:
- 没有:构造一个Node节点存放在 table[index] 中;
- 有数据:在当前index下的结构(链表或红黑树),查找该key值,判断Key值是否存在相等
- 相等:使用新value替换原数据,返回旧value(如果
onlyIfAbsent
参数给true
则啥都不做) - 不相等:执行插入流程
- 判断节点类型:
- 树形节点:创造树型节点插入红黑树中
- 链表节点:创建Node加入链表尾部,并判断是否
链表长度 > 8 且 数组长度 > 64
,是的话该链表转为红黑树
- 插入完成,size是否大于扩容阈值
size > maxSize * 0.75
,是的话扩容为原数组的二倍
- 判断节点类型:
- 相等:使用新value替换原数据,返回旧value(如果
4. JDK1.8的优化
- 结构优化,数据结构由数组+链表 改为了 数组+链表/红黑树
- 避免链表过长导致查询效率差,时间复杂度由
O(n)
降低为O(logn)
- 数据量较多或者hash散列性不够,都可能导致链表过长
- 红黑树的插入和查询效率处于完全平衡二叉树和链表之间
- 避免链表过长导致查询效率差,时间复杂度由
- 扩容优化:原本扩容会将key重新进行hash定位,后改为判断hashCode高位,高位为0则index不变,为1则
index = index + 旧数组大小
- 链表的插入方式由头插法改成尾插法(新节点放在链表尾部):因为头插法会发生链表翻转,在多线程的情况下碰到扩容,存在产生环的风险
- 由先判断扩容再插入,改为插入完成后再判断扩容
PS:参考 https://blog.csdn.net/zhengwangzw/article/details/104889549 上面有详细的原理解释