1. 程式人生 > 其它 >HashMap原始碼分析解析

HashMap原始碼分析解析

技術標籤:後端進階javahashmap連結串列

HashMap原始碼解析-更新中...


雖盡全力認真學習解讀,但是難免還是會有疏漏,請各位大佬蒞臨指導,指出錯誤,必定認真修改。及時更新,感謝感謝!!


1、屬性解析

1.1 hash初始長度

  • 最主要還是Hash表的內容,預設初始長度為16,擴容需為2的冪,也就是左移一位
	/**
     * The default initial capacity - MUST be a power of two.
	 * 這是預設的容量,必須為2的倍數,目前預設為 2^4 = 16
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

1.2 負載擴容的因子

  • 為啥預設的擴充套件因子為0.75,說法眾說紛紜
    • 其中一個如果為0.5,會導致hash表的內容過大,如果為1的話,會導致重複的hash較多,達不到快速讀取的效果
    • 我個人的看法,16*0.75 = 12 , 16(右移)擴容一倍為32,12右移一位為24
      12 二進位制 0000 1100 = 8+4 = 12 右移一位 為0001 1000 = 16+8 = 24
      實際計算,最佳的擴充套件因子等於0.6931 1 ,根據上述情況,0.75很方便計算。
	/**
     * The load factor used when none specified in constructor.
	 * 預設讀取的負載 = 0.75,容量充滿75%以及以上是,進行擴容,擴容一倍 
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    /**
     * The load factor for the hash table.
     * hash表的載入擴充係數
     */
final float loadFactor;

1.3 單鏈表和二叉樹轉換

  • java8,hash地址碰撞之後,使用的鏈地址法。
    示例圖形
    轉換的過程,當單鏈表的長度大於等8時,會轉換為紅黑樹進行儲存(ps:實際程式碼跟蹤下來,其實雙鏈表和紅黑樹共存),當具體的值小於6時,又會拆成單鏈表。
   /**
	 * jdk1.8中,hash衝突時,資料內容放在連結串列中,當連結串列的長度超過8時,則轉化為紅黑樹
	 * 連結串列和紅黑樹的轉換閾值,最小應該為2,至少應該等於8
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
	 * 修改紅黑樹為連結串列的閾值,
     */
    static final int UNTREEIFY_THRESHOLD = 6;

1.4 容量最大值和閾值最大值

  • 容量預設最大值
	/**
	 * 最大的容量,小於等於2^30
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;
  • 最大的閾值
	/**
     * The next size value at which to resize (capacity * load factor).
     * 下一次擴容的閾值 (當前容量 * 擴充係數loadFactor)
     */
    int threshold;
    ......
	if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
    }

1.5 計算hash值,如何對映

  • 計算hash值
   /**
	 * 大致的意思,計劃hash內容變得比之前簡化,高16位和低16位進行異或操作獲取hash值,對於容易出現衝突的一點,使用連結串列和紅黑樹進行處理
	 >> 帶符號右移 >>> 無符號右移,高位全部補0
     */
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    ...... 
    static class Node<K,V> implements Map.Entry<K,V> {
	......
		// 獲取當前的hash的值
        public final int hashCode() {
			// key值得hashCode 異或(相同為0不同為1) 值的hashCode
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
	......
	}
  • 如何進行對映
    • tab指 hashTable的列表
    • n只當前容量大小
tab[(n - 1) & hash]

1.6 hash擴容

具體程式碼提示,在下文中展示

  1. 針對原始的連結串列內容
    • 待完成
  2. 針對連結串列和紅黑樹
    • 連結串列:根據新的容量 (n)&hashCode>0,擴容的部分,hash有無高位,無高位歸原始hash值連結串列,有高位歸屬新的hash值連結串列
    • 紅黑樹: 根據新的容量 (n)&hashCode>0,擴容的部分,hash有無高位,之後判斷是否達到維持紅黑樹的最小長度,再判斷無高位歸屬原始hash,是轉換為連結串列還是保持紅黑樹。高位的更新hash其餘一致。

1.7 支援不支援null值

null的hash值是0,預設在hash表的第一個

2、實現的介面&繼承類的解析

3、程式碼分段解析

3.1 儲存節點解析

   /**
	 * hash表的內容,擴容必須為2的冪
     */
    transient Node<K,V>[] table;
    
	......
	
	// 初始化table列表
	@SuppressWarnings({"rawtypes","unchecked"})
	Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
	table = newTab;
	
    ......

   /**
	 * 基本的節點
     */
    static class Node<K,V> implements Map.Entry<K,V> {
		// hash值是不能改變的,否則下一次就找不到這個值了
        final int hash;
		// 儲存的鍵,鍵也不能更改,一般建議為Int或者String
        final K key;
		// 儲存的物件
        V value;
		// 連結串列的下一個指標
        Node<K,V> next;
		// 初始化
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }
		// 獲取當前的hash的值
        public final int hashCode() {
			// key值得hashCode 異或(相同為0不同為1) 值的hashCode
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
		// 設定新值並返回舊值
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
		// 內容對比,要麼引用物件相等,要麼型別相同,並且鍵值相等
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

3.2 紅黑樹節點解析

4、演算法解析


參考文獻

注:原始碼部分,來自java原始碼2,註釋是自己寫的,刪除或者部分刪除原來的英文註釋


  1. 為什麼HashMap的負載因子是0.75 ↩︎

  2. java8原始碼 ↩︎