public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 初始化桶数组 table,table 被延迟到插入新数据时再进行初始化 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 如果桶中不包含键值对节点引用,则将新键值对节点的引用存入桶中即可 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; // 如果键的值以及节点 hash 等于链表中的第一个键值对节点时,则将 e 指向该键值对 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 如果桶中的引用类型为 TreeNode,则调用红黑树的插入方法 elseif (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { // 对链表进行遍历,并统计链表长度 for (intbinCount=0; ; ++binCount) { // 链表中不包含要插入的键值对节点时,则将该节点接在链表的最后 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // 如果链表长度大于或等于树化阈值,则进行树化操作 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } // 条件为 true,表示当前链表包含要插入的键值对,终止遍历 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 判断要插入的键值对是否存在 HashMap 中 if (e != null) { // existing mapping for key VoldValue= e.value; // onlyIfAbsent 表示是否仅在 oldValue 为 null 的情况下更新键值对的值 if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // 键值对数量超过阈值时,则进行扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); returnnull; }
首次扩容:先判断数组是否为空,若数组为空则进行第一次扩容
计算索引:通过 hash 算法,计算键值对在数组中的索引
插入数据
如果当前位置元素为空,则直接插入数据
如果当前位置元素非空,且 key 已存在,则直接覆盖其 value
如果当前位置元素非空,且 key 不存在,则将数据链到链表末端
若链表长度 >= 8 且数组长度 >= 64,则将链表转换成红黑树,并将数据插入树中
再次扩容:如果数组中元素个数超过阈值,则再次进行扩容操作
扩容机制
哈希表计算
下面这个方法保证了 HashMap 总是使用 2 的幂作为哈希表的大小
1 2 3 4 5 6 7 8 9 10 11 12
/** * 找到大于或等于 cap 的最小 2 的幂 */ staticfinalinttableSizeFor(int cap) { intn= cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
finalvoidtreeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; // 如果表的长度小于 64 会先扩容!!! 否则 扩容 // MIN_TREEIFY_CAPACITY = 64; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); elseif ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } }
为什么是链表长度为 8?
1 2 3 4 5 6 7 8 9 10
* 0: 0.60653066 * 1: 0.30326533 * 2: 0.07581633 * 3: 0.01263606 * 4: 0.00157952 * 5: 0.00015795 * 6: 0.00001316 * 7: 0.00000094 * 8: 0.00000006 * more: less than 1 in ten million
当 hashCode 离散性很好的时候,树形 bin 用到的概率非常小,因为数据均匀分布在每个 bin 中,几乎不会有 bin 中链表长度会达到阈值,在理想情况下随机 hashCode 算法下所有 bin 节点的分布频率会遵循泊松分布,一个 bin 中链表长度达到 8 个元素的概率为 0.00000006,几乎是不可能事件
abstractclassHashIterator { Node<K,V> next; // next entry to return Node<K,V> current; // current entry int expectedModCount; // for fast-fail int index; // current slot
HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // advance to first entry // 寻找第一个包含链表节点引用的桶 do {} while (index < t.length && (next = t[index++]) == null); } }
publicfinalbooleanhasNext() { return next != null; }
final Node<K,V> nextNode() { Node<K,V>[] t; Node<K,V> e = next; if (modCount != expectedModCount) thrownewConcurrentModificationException(); if (e == null) thrownewNoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { // 寻找下一个包含链表节点引用的桶 do {} while (index < t.length && (next = t[index++]) == null); } return e; } //省略部分代码 }