(java 8)HashMap-resize()解读
final Node<K,V>[] resize() { // 扩容前的数组 Node<K,V>[] oldTab = table; // 扩容前的数组的大小和阈值 int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; // 预定义:新数组的大小和阈值 int newCap, newThr = 0; if (oldCap > 0) { // 数组最大容量不能超过MAXIMUM_CAPACITY,超过了就不会再扩容 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 否则扩大为当前容量的两倍,并且也是不能超过MAXIMUM_CAPACITY else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } // 当前数组没有数据,使用初始化的值 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults // 如果初始化的值,为0,则使用默认的出初始化容量 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 阈值也是不能超过MAXIMUM_CAPACITY if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 开始扩容,将新的容量赋值给table table = newTab; // 之前有数据,这里需要转移,重点是里面的do循环 if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { // 这里设置了当前数组的为null,可以防止其他线程(也在扩容)进入if ((e = oldTab[j]) != null)这个代码块吧,猜测 oldTab[j] = null; // 这个数组只保存了一个数据时,扩容之后肯定还是在新数组的某个桶下的第一个元素,为什么? // 扩容的时候是链表顺序遍历的,头结点肯定先被遍历到,新数组的位置还是在头结点位置 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) // 红黑树相关的处理 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order // loHead 表示老值,老值的意思是扩容后,该链表中计算出索引位置不变的元素 // hiHead 表示新值,新值的意思是扩容后,计算出索引位置发生变化的元素 // 举个例子,数组大小是 8 ,在数组索引位置是 1 的地方挂着一个链表,链表有两个值,两个值的 hashcode 分别是是9和33。 // 当数组发生扩容时,新数组的大小是 16,此时 hashcode 是 33 的值计算出来的数组索引位置仍然是 1,我们称为老值 // hashcode 是 9 的值计算出来的数组索引位置是 9,就发生了变化,我们称为新值。 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; // 这个的效果就是,假如一开始的容量是8,现在扩容到16,那么hash是{0~7}值与8都是0,大于等于8的hash与8都是8(不等于0) // &运算 可以理解为,两个 "101010" 这种字符串 取"1"交集的运算 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); // 最后再把新值和老值的链表,才给数组赋值 // java 7 是在 while 循环里面,单个计算好数组索引位置后,单个的插入数组中,在多线程情况下,会有成环问题 // java 8 是等链表整个 while 循环结束后,才给数组赋值,所以多线程情况下,也不会成环 if (loTail != null) { loTail.next = null;
// 链表的头结点 newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null;
// 链表的头结点 newTab[j + oldCap] = hiHead; } } } } } return newTab; }
总结:
1.数组最大容量不能超过MAXIMUM_CAPACITY,超过了就不会再扩容
2.扩大为当前容量的两倍,并且也是不能超过MAXIMUM_CAPACITY
3.扩容阈值也是不能超过MAXIMUM_CAPACITY
4.java 7 在 while 循环里面,单个计算好数组索引位置后,单个的插入数组中,在多线程情况下,会有成环问题
java 8 是等链表整个 while 循环结束后,才给数组赋值,所以多线程情况下,也不会成环
https://github.com/Enast/hummer