手撕源码----jdk 8.0HashMap底层源码 2
作者:互联网
我们在手撕HashMap1中已经详细介绍了如下几种方法,如果想重温的可以点击这里 -> HashMap1
void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) //把传入的map容器里的Entry对象填充到当前容器中 Node<K,V> getNode(int hash, Object key) //根据传入的key以及它的哈希值在table数组以及链表中找到那个节点 V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)
//把键值对插入进map容器,根据参数onlyIfAbsent决定如果key值相同value是否改变
今天我们来看看一个十分重要的方法:
resize():
1 final Node<K,V>[] resize() { 2 Node<K,V>[] oldTab = table; 3 int oldCap = (oldTab == null) ? 0 : oldTab.length; 4 int oldThr = threshold; 5 int newCap, newThr = 0; 6 if (oldCap > 0) { 7 if (oldCap >= MAXIMUM_CAPACITY) { 8 threshold = Integer.MAX_VALUE; 9 return oldTab; 10 } 11 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 12 oldCap >= DEFAULT_INITIAL_CAPACITY) 13 newThr = oldThr << 1; // double threshold 14 } 15 else if (oldThr > 0) // initial capacity was placed in threshold 16 newCap = oldThr; 17 else { // zero initial threshold signifies using defaults 18 newCap = DEFAULT_INITIAL_CAPACITY; 19 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 20 } 21 if (newThr == 0) { 22 float ft = (float)newCap * loadFactor; 23 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 24 (int)ft : Integer.MAX_VALUE); 25 } 26 threshold = newThr; 27 @SuppressWarnings({"rawtypes","unchecked"}) 28 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 29 table = newTab; 30 if (oldTab != null) { 31 for (int j = 0; j < oldCap; ++j) { 32 Node<K,V> e; 33 if ((e = oldTab[j]) != null) { 34 oldTab[j] = null; 35 if (e.next == null) 36 newTab[e.hash & (newCap - 1)] = e; 37 else if (e instanceof TreeNode) 38 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 39 else { // preserve order 40 Node<K,V> loHead = null, loTail = null; 41 Node<K,V> hiHead = null, hiTail = null; 42 Node<K,V> next; 43 do { 44 next = e.next; 45 if ((e.hash & oldCap) == 0) { 46 if (loTail == null) 47 loHead = e; 48 else 49 loTail.next = e; 50 loTail = e; 51 } 52 else { 53 if (hiTail == null) 54 hiHead = e; 55 else 56 hiTail.next = e; 57 hiTail = e; 58 } 59 } while ((e = next) != null); 60 if (loTail != null) { 61 loTail.next = null; 62 newTab[j] = loHead; 63 } 64 if (hiTail != null) { 65 hiTail.next = null; 66 newTab[j + oldCap] = hiHead; 67 } 68 } 69 } 70 } 71 } 72 return newTab; 73 }
这个方法十分的长,别急,我们一行一行分析代码:
2 Node<K,V>[] oldTab = table; 3 int oldCap = (oldTab == null) ? 0 : oldTab.length; 4 int oldThr = threshold; 5 int newCap, newThr = 0;
首先算出修改前的数组的最大容量以及扩容的临界值。
6 if (oldCap > 0) { 7 if (oldCap >= MAXIMUM_CAPACITY) { 8 threshold = Integer.MAX_VALUE; 9 return oldTab; 10 } 11 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 12 oldCap >= DEFAULT_INITIAL_CAPACITY) 13 newThr = oldThr << 1; // double threshold 14 }
根据修改前数组的容量进行讨论,这里是不处于构建模式:
①. 如果修改前数组的容量比规定的最大长度都大了,那么只好把扩容临界值也调整为最大值并返回旧数组。
②. 否则的话,新数组长度等于旧数组长度 x 2,并且新数组长度要求小于规定的最大长度,旧数组长度大于默认的数组长度,扩容临界值也改变为原来2 倍
因为扩容长度和数组长度成正比。
15 else if (oldThr > 0) // initial capacity was placed in threshold 16 newCap = oldThr; 17 else { // zero initial threshold signifies using defaults 18 newCap = DEFAULT_INITIAL_CAPACITY; 19 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 20 }
这里是旧数组容量等于0(构造模式)
如果旧扩容临界值大于0(这里很奇怪,为啥明明旧数组容量都等于0了还有扩容临界值),新数组容量等于旧扩容临界值。
否则(旧扩容临界值等于0)新数组长度和新扩容容量赋值为默认值。
21 if (newThr == 0) { 22 float ft = (float)newCap * loadFactor; 23 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 24 (int)ft : Integer.MAX_VALUE); 25 }
如果新扩容容量仍然为0的话,根据公式
* threshold : 扩容的临界值 = 容量 * 填充因子
进行重新赋值,并且如果新数组容量和以及新扩容的临界值小于最大值的话,就对其重新赋值,否则赋值为整数的最大值。
26 threshold = newThr; 27 @SuppressWarnings({"rawtypes","unchecked"}) 28 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 29 table = newTab;
根据之前算出来的新扩容临界值和新数组容量为当前容器更新数组以及扩容临界值。
在确定新的数据之后,就要把原来的内容复制到新的容器中:
如果resize()之前容器内部数组存在的话:
遍历数组位置上的每个元素(头结点),并用节点Node<K,V> e指向它
34 oldTab[j] = null; 35 if (e.next == null) 36 newTab[e.hash & (newCap - 1)] = e;
如果把数组位置上置为null,并且如果当前节点指向的下一个节点不存在的话把当前指向的节点复制到新的数组中
当然这里的哈希值要根据新的数组重新确定一遍
37 else if (e instanceof TreeNode) 38 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
如果这个节点是红黑树中的节点的话就用树结构封装的方法放置即可。
如果这个链表存在下一个节点并且不是红黑树结构的话:
40 Node<K,V> loHead = null, loTail = null; 41 Node<K,V> hiHead = null, hiTail = null; 42 Node<K,V> next; 43 do { 44 next = e.next; 45 if ((e.hash & oldCap) == 0) { 46 if (loTail == null) 47 loHead = e; 48 else 49 loTail.next = e; 50 loTail = e; 51 } 52 else { 53 if (hiTail == null) 54 hiHead = e; 55 else 56 hiTail.next = e; 57 hiTail = e; 58 } 59 } while ((e = next) != null);
其中:
40 Node<K,V> loHead = null, loTail = null; 41 Node<K,V> hiHead = null, hiTail = null; 42 Node<K,V> next;
是这样理解的:
红线左边的是旧数组容量部分
红线右边是新数组相对比于旧数组新开辟出来的位置。
loHead和loTail分别指向新数组中红线左边的位置的头结点以及头结点所在的链表的尾结点。
hiHead和hiTail分别指向新数组中红线右边的位置的头结点以及头结点所在的链表的尾结点。
43 do { 44 next = e.next; 45 if ((e.hash & oldCap) == 0) { 46 if (loTail == null) 47 loHead = e; 48 else 49 loTail.next = e; 50 loTail = e; 51 } 52 else { 53 if (hiTail == null) 54 hiHead = e; 55 else 56 hiTail.next = e; 57 hiTail = e; 58 } 59 } while ((e = next) != null);
这里的判断条件是我理解最困难的地方了,我想了一阵后才明白
(e.hash & oldCap) == 0
是用来界定lo和hi的分界线,这里就要用到数学知识了,因为在HashMap中哈希值是这样计算的:
e.hash & (oldCap - 1)
这里和上面的表达式只有一个-1的差别但是算出来的得数却是天差地别。
因为数组的容量一定是2的次方,所以capcity = 10000...000(B)
所以一个小于capacity的数&运算后一定是0,但是如果是&(capacity-1) 得数一定是他自己
这里的计算实在是太美妙了
确定分界线后就可以让loHead,loTail把这里的链表串起来了,
下面的hiHead和hiTail同理。
60 if (loTail != null) { 61 loTail.next = null; 62 newTab[j] = loHead; 63 } 64 if (hiTail != null) { 65 hiTail.next = null; 66 newTab[j + oldCap] = hiHead; 67 }
串完后就把链表的头节点放入新数组即可
最后再返回新数组。
标签:Node,8.0,HashMap,loTail,next,源码,数组,null,oldCap 来源: https://www.cnblogs.com/apakchan/p/15251991.html