1. 程式人生 > 其它 >Java HashMap 原始碼閱讀

Java HashMap 原始碼閱讀

# 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)

      1. 先判斷當前的陣列是否被初始化,是否有值,以及 key 對應的陣列位置是否有值
        • key 對應的索引位置是對 key 的 hashcode 進行 hash 之後的值和陣列長度-1進行與操作得到的 tab[(n - 1) & hash]
      2. 判斷陣列該位置的連結串列的頭結點是否是要找的節點,先比較 hash,再比較 key 的地址,地址不等再用 equals 比較
      3. 如果頭結點不是,再判斷後面的節點
        • 如果頭結點之後的節點是樹節點,即已經變成紅黑樹了,呼叫 getTreeNode,在紅黑樹中尋找指定節點
        • 否則遍歷挨個比較,hash、地址、equals
    • 詳細 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

      • @param onlyIfAbsent if true, 不改變已存在的值
      • @param evictif false,table 在建立模式
      1. 如果底層陣列還沒有初始化,就先呼叫 resize 函式進行初始化,是在第一次 put 的時候進行的初始化
      2. 如果陣列的當前位置還沒有節點,就將當前的鍵值對封裝成頭結點
      3. 如果已經有頭結點了
        1. 如果頭結點就是和要 put 的節點的 key 一樣,記錄為 e
        2. 如果頭結點是樹節點,呼叫 putTreeVal 向紅黑樹中新增該節點,如果紅黑樹中已經存在 key 相同的節點,返回該節點,記錄為 e
        3. 如果頭結點是連結串列節點
          1. 如果遍歷到連結串列的尾部,在尾部新建一個節點,用當前鍵值對初始化,如果此時達到了樹化閾值,執行 treeifyBin 將連結串列轉化成紅黑樹
          2. 如果遍歷連結串列找到了 key 值相同的節點,該節點為 e,停止遍歷
        4. 如果 e 不為空,即連結串列或紅黑樹中存在與 key 相同的節點,用新值更新舊值,函式 return
        5. 如果 e 為空,說明在原來的基礎上添加了新的節點
      4. 如果沒有加新的節點,而是在原有的節點上修改了 value,就已經返回了,走不到這一步。如果添加了新的節點, modCount 修改次數 +1,size 鍵值對數+1,如果鍵值對數達到了閾值,進行 resize 擴容
    • 詳細 final Node<K,V>[] resize()

      1. 如果原陣列的容量已經 ≥ 最大容量,則將閾值設定為 Integer.MAX_VALUE,否則新容量等於舊容量的 2 倍,新閾值等於舊閾值的 2 倍。
      2. 如果舊容量為 0,即未初始化,新容量設為初始化容量
      3. 新建一個新的容量的陣列,將所有元素重新計算hash值再重新分配到新陣列中
      4. 新計算的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);
          }
      }
      
      1. 如果陣列長度沒有達到最小樹化閾值,去進行擴容
      2. 否則,將陣列節點替換成樹節點,插入樹中