ps:没看太懂源码,不确定是否正确...
一、扩容条件
当HashMap中元素的总个数超过(threshold)阈值(数组容量乘以负载因子)时,会触发扩容。默认情况下,(capacity)数组初始容量为16,(loadFactor)负载因子为0.75。
二、扩容过程
1.JDK 1.7的HashMap扩容机制:
- 首先,生成老数组长度两倍的新数组。
- 遍历老数组中桶中的每个元素。
- 根据key值重新计算每个元素的索引下标,将每个元素按照新的下标添加到新的数组中。
- 所有元素转移完成之后,新数组取代原数组,完成扩容。
(注意:在JDK 1.7中,HashMap在扩容时会采用头插法插入链表元素,这可能会导致在多线程环境下出现链表成环的问题。而在JDK1.8中,采用尾插法和链表重新链接解决此问题。)
2.JDK 1.8的HashMap扩容机制:
(前面两点与JDK1.7的一样)
- 首先,生成老数组长度两倍的新数组。
- 遍历老数组中桶中的每个元素。
- 如果桶节点还未形成链表,则计算出该元素在新数组中的索引位置,转移到新数组中。
- 如果桶节点已经形成了红黑树,则调用split方法。遍历这个红黑树中的所有节点,按照(该节点的哈希值与(&)老数组的长度值是否为0)来把原来的红黑树节点拆分成低位区和高位区两组树节点(此时应该是不符合红黑树的性质的)到新的数组中,然后根据节点个数,如果节点个数 <= 6,则要去树化操作,转化为链表,节点个数 >6,则树化操作为红黑树。 (按照(e.hash & bit) 是否 == 0,低位区头节点位置==原来位置,高位区头节点位置==原来位置+老数组长度)。
- 如果桶节点已经形成链表,则要遍历这个链表所有节点,一样是按照(该节点的哈希值与(&)老数组的长度值是否为0)来把原来的链表拆分为低位区和高位区两个链表重新分配到新数组中。(按照(e.hash & oldCap)是否 == 0,一样低位区头节点位置==原来位置,高位区头节点位置==原来位置+老数组长度)。
- 所有元素转移完成之后,新数组取代原数组,完成扩容。
注意:
- 因为老数组的初始容量为2的次幂,扩容之后的长度是原来的两倍,即新数组的容量也是
2的次幂。又因为元素的下标位置计算为tab[i = (n - 1) & hash],则下标 i 的二进制形式要么比原来左边新增一个0,或者新增一个1,则元素在新数组中的索引位置要么==原索引位置,要么==原索引位置+老数组长度)。 - JDK1.8的HashMap默认内部的数组是null,即是没有实例化的。第一次调用put方法时,才会开始第一次初始化扩容,长度为16。即第一次扩容是初始化一个数组,而不是上面步骤,上面步骤是已经了第一次扩容的情况。
看源码:
if ((e = oldTab[j]) != null) {oldTab[j] = null;//3.如果桶节点还未形成链表,则计算出该元素在新数组中的索引位置,转移到新数组中。if (e.next == null)newTab[e.hash & (newCap - 1)] = e;
//4.如果桶节点已经形成了红黑树,则调用split方法。else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//5.如果桶节点已经形成链表,则要遍历这个链表所有节点,一样是按照(该节点的哈希值与(&)老数组的长度值是否为0)来把原来的链表拆分为低位区和高位区两个链表重新分配到新数组中。else { // preserve orderNode<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}/*** Splits nodes in a tree bin into lower and upper tree bins,* or untreeifies if now too small. Called only from resize;* see above discussion about split bits and indices.** @param map the map* @param tab the table for recording bin heads* @param index the index of the table being split* @param bit the bit of hash to split on*/
//如果桶节点已经形成了红黑树,则调用split方法。遍历这个红黑树中的所有节点,按照(该节点的哈希值与(&)老数组的长度值是否为0)来把原来的红黑树节点拆分成低位区和高位区两组树节点(此时应该是不符合红黑树的性质的)到新的数组中,然后根据节点个数,如果节点个数 <= 6,则要去树化操作,转化为链表,节点个数 >6,则树化操作为红黑树。 final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {TreeNode<K,V> b = this;// Relink into lo and hi lists, preserving orderTreeNode<K,V> loHead = null, loTail = null;TreeNode<K,V> hiHead = null, hiTail = null;int lc = 0, hc = 0;for (TreeNode<K,V> e = b, next; e != null; e = next) {next = (TreeNode<K,V>)e.next;e.next = null;if ((e.hash & bit) == 0) {if ((e.prev = loTail) == null)loHead = e;elseloTail.next = e;loTail = e;++lc;}else {if ((e.prev = hiTail) == null)hiHead = e;elsehiTail.next = e;hiTail = e;++hc;}}if (loHead != null) {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);}}}