不管是JDK1.7或者JDK1.8 当put方法执行的时候,如果table为空,则执行resize();方法扩容。默认长度为16;
JDK1.7扩容
条件:发生扩容的条件必须同时满足两点
- 当前存储的数量大于等于阈值
- 发生hash碰撞
特点:先扩容,再添加(头插法)
缺点:头插法会使链表发生反转,多线程环境下会产生环
扩容之后对table的调整:
table容量变为2倍,元素下标需要重新计算
重新获取key的hashcode 再对hashcode进行扰动(具体解释可以翻我文章)
newinex = hash (扰动后) & (length - 1)
JDK1.8扩容
条件:当前存储的数量大于等于阈值
特点:尾插法,先插后判断是否需要扩容
缺点:多线程下,1.8会有数据覆盖
举例:
线程A:往index插,index此时为空,可以插入,但是此时线程A被挂起
线程B:此时,对index写入数据,A恢复后,就把B数据覆盖了
扩容之后对table的调整:
table容量变为2倍,元素下标可以直接定位原节点在新的table中的位置,怎么计算呢?
由于table扩大为2倍,相当于低位掩码高位多了一个 1 ,例如原来length是16其length - 1 的二进制 是 0000 1111 ,扩大为32之后 length - 1的二进制是 0001 1111 ,再回到计算下标的公式 newinex = hash (扰动后) & (length - 1),扩大之后低位掩码高位多了一个 1 ,因为1 和 1与是1,和0与是0,所以这里的newindex 的值就取决于 hash(扰动后) 的倒数第五位的值。
(关于具体取index 的介绍可以翻我文章)
如果这个hash倒数第五位的值 是 0 与之后还是0 就和扩容前的值一样,所以下标不变;
如果这个hash倒数第五位的值 是 1 与之后是1 就比扩容前的值翻了一倍,所以下标是原下标的二倍;
关于链表转红黑树:
当插入之后,判断链表的长度 > 8?且数组长度 > 64 都满足则转为红黑树;
否则判断是否大于阈值,大于则扩容。
- 为什么这里是8?
时间和空间的折衷,红黑树节点大小约为链表节点的2倍,在节点太少时,红黑树的查找性能并不明显,付出2倍的空间代价不值得。
对于移除,移除后达到6,且该节点是红黑树节点 则转为链表。
- 为什么是6?
当节点个数在8徘徊时,频繁的转换,性能损耗大。