深入理解Java之HashMap —— 01

《精要主義》書中的一些感悟:

幾乎沒有事物的次要性,再怎麼高估都不過分。試着利用精要主義打破用忙碌衡量成功的淺見吧。


無論是在工作or面試中,HashMap都是我們經常打交道的。所以今天我們源碼的角度來窺探其奧妙。

1.HashMap的結構


1.1 JDK1.7版本

JDK1.7以前的設計,HashMap採用數組+單鏈表的實現方式,鏈表就是用來處理Hash衝突的

學過數據結構的人都知道,鏈表有個缺點:查找速度慢因爲必須從頭查詢,時間複雜度是O(n)

在這裏插入圖片描述

1.2 JDK1.8版本

所以在JDK 1.8 對這裏做了優化,因爲我們知道樹型結構可以將查找時間複雜度降爲O(lg n)

在這裏插入圖片描述

在鏈表節點數量超過一定量(8)時,則將鏈表轉化爲紅黑樹, 以後找時間專門來了解一下紅黑樹。

1.3 關於HashMap結構的總結

HashMap的結構

JDK 1.7 : 數組 + 單鏈表 (注意這裏強調一下
JDK 1.8 :數組 + 單鏈表 + 紅黑樹

1.(單)鏈表的目的:解決了Key的hash衝突問題;
2. 紅黑樹的目的:加快查詢速度;


2. 設計HashMap

2.1 HashMap中的常量值

  1. 默認容量16;
  2. 最大容量2^30;
  3. 默認負載因子 0.75
  4. 可樹化的閥值 8 (鏈表—> 紅黑樹);
  5. 去樹化的閥值 6 (紅黑樹–>鏈表 );
  6. 哈希表考慮樹形化的最小數量 64;
     /**
     * 默認的初始化變量(16)-必須是2的冪(次方)
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 最大容量(必須是2的冪且 <= 2的30次方)
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默認的裝載因子(填充比例):0.75,即實際元素所佔所分配的容量的75%時就要擴容啦。
     * 如果該值過大,再增加元素將導致鏈表過長,查找效率降低;反之,浪費空間
     * 關注內存:適當增大填充比;
     * 關注查找性能: 適當減小填充比;
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 每個桶的可樹化的閥值,即當桶中元素個數超過該值時,自動將鏈表轉化爲紅黑樹結構
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 每個桶的去樹化閥值,即在擴容(resize())時,發現桶中的元素少於該值,則將樹形結構還原爲鏈表結構
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 哈希表中可被樹形化的最小數量。
     * 即若哈希表中的元素個數少於該值,則桶(bin)不會被樹形化,而是會進行擴容(就算桶中的元素個數超過TREEIFY_THRESHOLD)。
     * 主要是爲了避免擴容&可樹化的選擇衝突
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

HashMap的跟容量相關的都必須是2的N次方。

2.2 HashMap中的變量

2.2.1 關鍵字-transient

用transient關鍵字標記的成員變量不參與序列化過程,即不會被序列化

2.2.2 HashMap中的變量

如果我們在設計HashMap時,這些基本我們都是要進行考慮的。(PS: 調整次數modCount 確實不知道有什麼用, ~233333)

  /***
     * 我們都知道HashMap底層數據結構是:數組+鏈表
     * 這個參數就是數組,即我們常說的:桶數組。
     * table 在「第一次使用時進行初始化」並在需要的時候重新調整自身大小。對於 table 的大小必須是2的冪次方。
     */
    transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * Map中的鍵值對的數量
     */
    transient int size;

    /**
     * HashMap 進行結構性調整的次數。結構性調整指的是增加或者刪除鍵值對等操作,注意對於更新某個鍵的值不是結構特性調整。
     */
    transient int modCount;

    /**
     * The next size value at which to resize (capacity * load factor).
     * Map的容量的閥值:表的大小 capacity * load factor,達到這個容量時進行擴容操作。
     */
    int threshold;

    /**
     * The load factor for the hash table.
     * 負載因子,默認值爲 0.75
     */
    final float loadFactor;

目前爲止,我們只看到了HashMap結構中的桶數組: transient Node<K,V>[] table;
看鏈表其實就是看節點Node的。

2.3 HashMap鏈表節點

單鏈表

	/**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     * Node 實現了Map.Entry接口
     */
    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) {
            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; }

        public final int 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;
        }
    }

2.4 HashMap紅黑樹節點

注意這裏繼承的是LinkedHashMap.LinkedHashMapEntry<K,V>

LinkedHashMap.LinkedHashMapEntry<K,V> 是雙向鏈表,本質上還是繼承自Node的。

  static final class TreeNode<K,V> extends LinkedHashMap.LinkedHashMapEntry<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) {
            super(hash, key, val, next);
        }

        /**
         * Returns root of tree containing this node.
         */
        final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }
       ...
}

這篇文章就到這裏,乾貨貌似不多…

因爲這篇文章的目的主要是讓大家對於HashMap有個概括上的認識。

歡迎繼續閱讀下一篇 深入理解Java之HashMap —— 02

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章