Java HashMap 原始碼閱讀
阿新 • • 發佈:2021-07-22
# HashMap
-
內部類
static class Node<K,V> implements Map.Entry<K,V> final class KeySet extends AbstractSet<K> final class Values extends AbstractCollection<V> final class EntrySet extends AbstractSet<Map.Entry<K,V>> abstract class HashIterator final class KeyIterator extends HashIterator final class ValueIterator extends HashIterator final class EntryIterator extends HashIterator static class HashMapSpliterator<K,V> static final class KeySpliterator<K,V> static final class ValueSpliterator<K,V> static final class EntrySpliterator<K,V> static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>
-
詳細
Node
static class Node<K,V> implements Map.Entry<K,V>{ final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) public final K getKey() public final V getValue() public final String toString() public final int hashCode() public final V setValue(V newValue) public final boolean equals(Object o) }
- 一個 Node 節點包含一個 hash,key,value 和下一個 Node,即是一個單向連結串列結構
- hash 是當前節點的 key 的 hashcode 經過 HashMap 的 hash 函式的 hash 值
- table 的同一位置的連結串列,Node 的 hash 和 陣列長度減 1 與的結果相等,但陣列長度的高位為 0 的位 不同 Node 的 hash 值可能不等
-
詳細
TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; TreeNode(int hash, K key, V val, Node<K,V> next) final TreeNode<K,V> root() static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) final TreeNode<K,V> find(int h, Object k, Class<?> kc) final TreeNode<K,V> getTreeNode(int h, Object k) static int tieBreakOrder(Object a, Object b) final void treeify(Node<K,V>[] tab) final Node<K,V> untreeify(HashMap<K,V> map) final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab, int h, K k, V v) final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable) final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p) static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p) static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, TreeNode<K,V> x) static <K,V> boolean checkInvariants(TreeNode<K,V> t) }
-
TreeNode 繼承自 LinkedHashMap.Entry<K,V>,而 LinkedHashMap.Entry<K,V> 繼承自 HashMap.Node<K,V>,因此 TreeNode 是 Node 的子類,Node 的 next 指標可以指向 TreeNode
-
詳細
final TreeNode<K,V> getTreeNode(int h, Object k)
final TreeNode<K,V> getTreeNode(int h, Object k) { return ((parent != null) ? root() : this).find(h, k, null); }
- 要根據 hash 和 key 找到指定的 TreeNode
- 如果當前節點的 parent 不為空,表示該節點不是根節點,則呼叫 root() 遍歷到根節點,然後呼叫根節點的 find
- 如果當前節點的 parent 為空,表示該節點是根節點,呼叫該節點的 find
-
詳細
final TreeNode<K,V> find(int h, Object k, Class<?> kc)
final TreeNode<K,V> find(int h, Object k, Class<?> kc) { TreeNode<K,V> p = this; do { int ph, dir; K pk; TreeNode<K,V> pl = p.left, pr = p.right, q; if ((ph = p.hash) > h) p = pl; else if (ph < h) p = pr; else if ((pk = p.key) == k || (k != null && k.equals(pk))) return p; else if (pl == null) p = pr; else if (pr == null) p = pl; else if ((kc != null || (kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0) p = (dir < 0) ? pl : pr; else if ((q = pr.find(h, k, kc)) != null) return q; else p = pl; } while (p != null); return null; }
- 紅黑樹的排序是根據 hash 值的大小排序的
- 如果當前節點的 hash 大於要查詢節點的 hash,找左節點
- 如果當前節點的 hash 小於要查詢節點的 hash,找右節點
- 如果等於
- 利用 == 和 equals 判斷當前節點是否是要找的節點,如果是,返回
- 如果左節點為空,找右節點
- 如果右節點為空,找左節點
- 如果定義了比較器,根據比較結果判斷下一步走左節點還是右節點
- 右節點呼叫 find 找到的節點不會空,說明找到了,返回
- 否則一定在左節點上
-
-
-
屬性
private static final long serialVersionUID static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 預設初始化容量 static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 預設載入因子 static final int TREEIFY_THRESHOLD = 8; // 樹化閾值 static final int UNTREEIFY_THRESHOLD = 6; // 樹退化閾值 static final int MIN_TREEIFY_CAPACITY = 64; // 最小樹化容量 transient Node<K,V>[] table; // 底層節點陣列 transient Set<Map.Entry<K,V>> entrySet; transient int size; // map 中鍵值對的數量 transient int modCount; // 修改次數 int threshold; // final float loadFactor; // hashtable 的載入因子
-
方法
public HashMap(int initialCapacity, float loadFactor) public HashMap(int initialCapacity) public HashMap() public HashMap(Map<? extends K, ? extends V> m) static final int hash(Object key) static final int tableSizeFor(int cap):將容量變成2的冪次 public int size() public boolean isEmpty() public V get(Object key):呼叫 getNode final Node<K,V> getNode(int hash, Object key) public boolean containsKey(Object key):呼叫 getNode,也需要對所有節點進行查詢 public V put(K key, V value):呼叫 putVal final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) final Node<K,V>[] resize() final void treeifyBin(Node<K,V>[] tab, int hash)
-
詳細
static final int hash(Object key)
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
- 擾動演算法,為了降低 hash 衝突的概率
- key 的 hashcode的高16位和低16位異或,因為陣列長度預設為16,保證所有的位都參與的索引的計算,減少只計算後 16 位導致 hash 衝突的概率
-
詳細
static final int tableSizeFor(int cap)
static final int tableSizeFor(int cap) { int n = 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; }
- 對已經是2的冪次的容量,減1之後最高位右移了一位,通過不斷的右移位或、將最高位之後的所有為都變成1,相當於都和最高位的1或了一次。最好再加1,相當於保持原來的值沒有變
- 對不是2的冪次的容量,減1之後最高位沒有變,將最高位之後的位全變成1後再加1,將容量變成了2的冪次
- 這種方法效率非常高
-
詳細
final Node<K,V> getNode(int hash, Object key)
- 先判斷當前的陣列是否被初始化,是否有值,以及 key 對應的陣列位置是否有值
- key 對應的索引位置是對 key 的 hashcode 進行 hash 之後的值和陣列長度-1進行與操作得到的
tab[(n - 1) & hash]
- key 對應的索引位置是對 key 的 hashcode 進行 hash 之後的值和陣列長度-1進行與操作得到的
- 判斷陣列該位置的連結串列的頭結點是否是要找的節點,先比較 hash,再比較 key 的地址,地址不等再用 equals 比較
- 如果頭結點不是,再判斷後面的節點
- 如果頭結點之後的節點是樹節點,即已經變成紅黑樹了,呼叫 getTreeNode,在紅黑樹中尋找指定節點
- 否則遍歷挨個比較,hash、地址、equals
- 先判斷當前的陣列是否被初始化,是否有值,以及 key 對應的陣列位置是否有值
-
詳細
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
@param onlyIfAbsent
if true, 不改變已存在的值@param evict
if false,table 在建立模式
- 如果底層陣列還沒有初始化,就先呼叫 resize 函式進行初始化,是在第一次 put 的時候進行的初始化
- 如果陣列的當前位置還沒有節點,就將當前的鍵值對封裝成頭結點
- 如果已經有頭結點了
- 如果頭結點就是和要 put 的節點的 key 一樣,記錄為 e
- 如果頭結點是樹節點,呼叫 putTreeVal 向紅黑樹中新增該節點,如果紅黑樹中已經存在 key 相同的節點,返回該節點,記錄為 e
- 如果頭結點是連結串列節點
- 如果遍歷到連結串列的尾部,在尾部新建一個節點,用當前鍵值對初始化,如果此時達到了樹化閾值,執行 treeifyBin 將連結串列轉化成紅黑樹
- 如果遍歷連結串列找到了 key 值相同的節點,該節點為 e,停止遍歷
- 如果 e 不為空,即連結串列或紅黑樹中存在與 key 相同的節點,用新值更新舊值,函式 return
- 如果 e 為空,說明在原來的基礎上添加了新的節點
- 如果沒有加新的節點,而是在原有的節點上修改了 value,就已經返回了,走不到這一步。如果添加了新的節點, modCount 修改次數 +1,size 鍵值對數+1,如果鍵值對數達到了閾值,進行 resize 擴容
-
詳細
final Node<K,V>[] resize()
- 如果原陣列的容量已經 ≥ 最大容量,則將閾值設定為 Integer.MAX_VALUE,否則新容量等於舊容量的 2 倍,新閾值等於舊閾值的 2 倍。
- 如果舊容量為 0,即未初始化,新容量設為初始化容量
- 新建一個新的容量的陣列,將所有元素重新計算hash值再重新分配到新陣列中
- 新計算的hash值要麼和原hash值相等,要麼是原hash值加上原來的陣列長度。設原陣列長為a,新陣列長度b=2a,那麼table[i]中的元素要麼在table[i]上要麼在table[i+a]上
-
詳細
final void treeifyBin(Node<K,V>[] tab, int hash)
final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((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); } }
- 如果陣列長度沒有達到最小樹化閾值,去進行擴容
- 否則,將陣列節點替換成樹節點,插入樹中
-