深究1.8版本HashMap源码
put方法
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
在putVal方法之前,对 key 进行了 hash 计算。
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
通过 hashCode() 方法和无符号左移16后的 hashCode 进行异或计算。 进行了一次扰动计算。
再看 putVal 方法中。如何计算 key 再数组中的下标。
1 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 2 boolean evict) { 3 Node<K,V>[] tab; Node<K,V> p; int n, i; 4 if ((tab = table) == null || (n = tab.length) == 0) 5 n = (tab = resize()).length; // 初始扩容 6 if ((p = tab[i = (n - 1) & hash]) == null) 7 tab[i] = newNode(hash, key, value, null); 8 else { 9 Node<K,V> e; K k; 10 if (p.hash == hash && 11 ((k = p.key) == key || (key != null && key.equals(k)))) 12 e = p; 13 else if (p instanceof TreeNode) // 判断节点是不是红黑树节点 14 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 加入红黑树中 15 else { 16 for (int binCount = 0; ; ++binCount) { 17 if ((e = p.next) == null) { // 将数据加在链表的尾部 18 p.next = newNode(hash, key, value, null); 19 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 20 treeifyBin(tab, hash); // 将链表转换成红黑树 21 break; 22 } 23 if (e.hash == hash && 24 ((k = e.key) == key || (key != null && key.equals(k)))) 25 break; 26 p = e; 27 } 28 } 29 if (e != null) { // existing mapping for key 30 V oldValue = e.value; 31 if (!onlyIfAbsent || oldValue == null) 32 e.value = value; 33 afterNodeAccess(e); 34 return oldValue; 35 } 36 } 37 ++modCount; 38 if (++size > threshold) 39 resize(); // 扩容 40 afterNodeInsertion(evict); 41 return null; 42 }
在第 6 行中, tab[i = (n – 1) & hash] 计算下标,使用了 hash 值跟 n-1 进行位与运算。
第一次初始容量为 16, hash 值跟 15 进行位 与运算。而 15 的二进制是 1111。得到的是 hash 值的前 4位二进制。所以得到的结果就是,0-15之间的数字,正好是数组下标。
假如容量已经扩展,那现在的容量为 2n,hash 值跟 2n-1 进行位 与运算, 得到的是 hash 值的前 n 位二进制。
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) { // 将hash 与 16 这种 2n 进行 与操作,计算新增的一位bit 是0,还是 1。 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) { // 0 该节点还存原下标位置 61 loTail.next = null; 62 newTab[j] = loHead; 63 } 64 if (hiTail != null) { // 1 该节点由原下标位置向后移动 2n 位置 65 hiTail.next = null; 66 newTab[j + oldCap] = hiHead; 67 } 68 } 69 } 70 } 71 } 72 return newTab; 73 }
新版 hashMap 对扩容进行了优化,当容量扩充为原来的 2 倍时,只需判断新增的一位 bit 是0还是1。
例如,当由 16 扩充至 32 时,16 的 二进制时 10000,与 hash 计算,得到的是第 5 位 bit 的值。
原本计算下标,只是与前 4 位进行与运算,现在扩容一次后是对前 5 位进行与运算。
扩容方法里面并没有重新计算所有位的与运算,只需判断新增的第 5 位。减少了计算量。
原创文章,如有什么不对,欢迎指出。