HashMap的源碼初理解

一 定義

基於哈希表的 Map,Cloneable, Serializable 接口的實現。與 HashTable 類相似,只不過 HashMap 允許 null 鍵與 null 值,而且HashMap 是非同步的,HashMap可以通過 Map m = Collections.synchronizeMap(hashMap) 語句進行同步。此類不保證映射的順序,特別是它不保證該順序恆久不變。如果經常迭代,爲性能考慮,儘量不要把初始容量設置得太高(或將加載因子設置得太低)。HashMap 的實例有兩個參數影響其性能,從源碼可知參數分別是  

int initialCapacity(初始容量), float loadFactor(加載因子),默認初始容量 16,加載因子 0.75,容量("桶"的數量)不足時重構增加容量至大約爲原始容量的2倍。

                                                                         HashMap的數據結構示意1.1

                                                                                    與其他類,接口關係1.2

二 屬性

1.序列號

 private static final long serialVersionUID = 362498820763181265L;

2.初始容量 16

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

3.默認加載因子

static final int MAXIMUM_CAPACITY = 1 << 30;

4.轉換紅黑樹通上的節點值

static final int TREEIFY_THRESHOLD = 8

HashMap 是採用 數組+鏈表 的方式進行存儲的,當其節點大於 8 時,存儲結構變爲 紅黑樹。

5.轉換鏈表桶上的節點值

static final int UNTREEIFY_THRESHOLD = 6;

當節點小於6時,存儲結構紅黑樹結構轉換爲鏈表。

6.轉換爲紅黑樹時紅黑樹的最小大小

static final int MIN_TREEIFY_CAPACITY = 64;

7.儲存元素的數組,大小爲2的冪次倍

transient Node<K,V>[] table;

8.儲存元素的集

transient Set<Map.Entry<K,V>> entrySet;

9.鍵值映射數目,不等於容量。

transient int size;

10.擴容與修改內部結構 次數的計數器,用於迭代器的快速失效

transient int modCount;

11.臨界值。當 實際大小(容量x加載因子)>臨界值 時,會進行擴容。

int threshold;

比如有1000個非重複鍵值對需要儲存時,此時 臨界值應 >=1000,若使用的默認擴充因子0.75,算出此時HashMap的容量至少應爲1333(1333*0.75=1000).這樣,存儲數據時纔不會自動擴容.又因爲tableSizeFor(int cap)方法,當你指定HashMap初始容量n之後,程序會用算法自動計算出一個不小於n的一個二的次冪值,所以直接指定容量爲2048.最終應new HashMap<T>(2048)

12.加載因子

final float loadFactor

 

三 構造函數

1.HashMap(int initialCapacity, float loadFactor)

 public HashMap(int initialCapacity, float loadFactor) {
        // 初始容量小與0
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        // 設置的初始容量過大,則默認爲最大容量
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        // 加載因子不大於0或非數字
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        //初始化加載因子
        this.loadFactor = loadFactor;
        // 初始化臨界值
        this.threshold = tableSizeFor(initialCapacity);
    }

2.HashMap(int initialCapacity)

public HashMap(int initialCapacity) {
        // 加載因子爲默認 0.75
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

3.HashMap()

public HashMap() {
        // 初始容量爲 16 ,加載因子爲 0.75 (均爲默認)
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

4.HashMap(Map<? extends K, ? extends V> m)

public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        // 將映射參數 m 中所有的元素添加到 HashMap 中
        putMapEntries(m, false);
    }

四 重要函數

平時使用的方法,如 get,put,remove......在方法體中一般都是調用其他方法,這裏主要對被調用的基本方法進行解釋

1.putMapEntries(Map<? extends K, ? extends V> m, boolean evict)

 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        // m 中含有元素,將其存放至 HashMap 實例中
        if (s > 0) {
            // 如果存放元素的數組未初始化
            if (table == null) { // pre-size
                // 計算出需要創建的 HashMap 容量
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                // 臨界值賦值
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            // 存放元素的數組已經初始化並且 m 大小大於臨界值,調用擴容方法
            else if (s > threshold)
                resize();
            // 循環,調用putVal(hash(key), key, value, false, evict)方法依次插入元素
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

2.putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 數組未初始化或長度爲0
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // (n - 1) & hash 確定元素存放在哪個桶中,桶爲空,新生成結點放入桶中
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        // 計算出來的桶已含有元素
        else {
            Node<K,V> e; K k;
            // 與桶中的第一個元素比較,如果 key 相等,替換原值爲準備加入的值
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // key 不相等,且爲紅黑樹節點
            else if (p instanceof TreeNode)
                // 放入樹中
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            // 爲鏈表結構
            else {
                // 再鏈表末端插入新節點
                for (int binCount = 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;
                    }
                    // 判斷鏈表中結點的key值與插入的元素的key值是否相等,相等則替換
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 在桶中找到與插入元素 key 值相等的節點
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                // 新值代替舊值
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                // 返回舊值
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 修改計數器 +1
        ++modCount;
        // 實際值大於臨界值擴容
        if (++size > threshold)
            resize();
        // 插入後回調
        afterNodeInsertion(evict);
        return null;
    }

3.resize()

 final Node<K,V>[] resize() {
        // 保存當前 table 爲 舊table
        Node<K,V>[] oldTab = table;
        // 當前 table 容量爲 舊容量
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // 保存當前 table 臨界值爲 舊臨界值
        int oldThr = threshold;
        // 新table 的 新容量 與 新臨界值
        int newCap, newThr = 0;
        /**
           resize()函數在size > threshold時被調用。oldCap大於 0 代表原來的 table 表非空,
           oldThr(threshold) 爲 oldCap × load_factor
        */
        if (oldCap > 0) {
            // 舊容量 HashMap 的最大容量
            if (oldCap >= MAXIMUM_CAPACITY) {
                // 臨界值返回 int 類整數的最大值是 2 的 31 次方 - 1 = 2147483647
                threshold = Integer.MAX_VALUE;
                // 無法擴容,返回 舊table
                return oldTab;
            }
            // 容量翻倍後爲 舊容量 的2倍並且小於最大容量,且 舊容量 大於默認容量16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                // 新臨界值 = 舊臨界值 翻倍
                newThr = oldThr << 1; // double threshold
        }
        // 舊臨界值 大於0
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        /**
          resize()函數在table爲空被調用。oldCap 小於等於 0 且 oldThr 小於等於0,用戶調用 HashMap()構造函數創建的 HashMap,所有值均採用默認值,oldTab(Table)表爲空,oldCap爲0,oldThr等於0,
        */
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 新臨界值 爲0
        if (newThr == 0) {
            // 新容量 與 加載因子 計算出 新臨界值 並賦值
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        // 修改臨界值爲 新臨界值
        threshold = newThr;
        // 初始化 table 爲計算出來的 新table
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        // 將 舊table 中的節點 rehash 到 新table 中
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    // 舊table 節點只有一個,直接在 新table 中定位
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    // 節點爲樹節點,進行紅黑樹的 rehash
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    // 鏈表
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        // 將同一桶中的元素根據(e.hash & oldCap)是否爲0進行分割,分成兩個不同的鏈表,完成rehash
                        do {
                            next = e.next;
                            //最高位爲0,這是索引不變的鏈表。
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //最高位爲1 (這是索引發生改變的鏈表)
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        // 原bucket位置的尾指針不爲空(即還有node)
                        if (loTail != null) {
                        	// 鏈表最後需要有一個null
                            loTail.next = null;
                            // 鏈表頭指針放在新桶的相同下標(j)處
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

4.getNode(int hash, Object key)

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // table 已經初始化且 桶 中 節點 不爲空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
        	// 與 桶 中第一個元素相同,返回第一個元素
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 桶 中含有多個 節點
            if ((e = first.next) != null) {
            	// 爲樹節點
                if (first instanceof TreeNode)
                	// 在樹中查找
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                // 爲鏈表,從鏈表第一個元素開始,若未找到,找至最後
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

5.removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable);

final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        // 如果 節點數組 tab 不爲空且長度 n 大於 0,並且根據hash定位到的節點對象p(該節點爲 樹的根節點 或 鏈表的首節點)不爲空,需要從該節點p向下遍歷,找到那個和key匹配的節點對象
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
        	// 初始化一個臨時的 節點,鍵,值
            Node<K,V> node = null, e; K k; V v;
        	// 如果當前節點的鍵和 key 相等,那麼當前節點就是要刪除的節點,賦值給node
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            // 至此並未匹配到,若 p 無下一節點,則找不到 key 對應節點,無法刪除,返回 null;若 p 有下一節點,則 p 爲鏈表或紅黑樹
            else if ((e = p.next) != null) {
            	// 紅黑樹型,調用 getTreeNode 方法從樹結構中查找滿足條件的節點
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                // 鏈表型,從頭到尾與傳入節點 key 進行對比
                else {
                    do {
                    	// 找到與 key 匹配節點,將節點賦值給 e 並跳出循環.
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        // 把當前節點 p 指向 e,讓 p 存儲的永遠爲下一次循環裏 e 的父節點,如果下一次 e 匹配上了,那麼 p 就是 node 的父節點
                        p = e;
                    } while ((e = e.next) != null);// 鏈表 e 含有下一個節點,則繼續匹配
                }
            }
            // 如果 node 不爲空,說明根據 key 匹配到了要刪除的節點;如果不需要對比該節點的 value值 或者需要對比 value值 ,且 value值 相等,那麼可以刪除該 node 節點
            if (node != null && (!matchValue || (v = node.value) == value || 
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

五 其他

有注意到,在很多地方都使用了 Node<K,V>,TreeNode<K,V> 這兩個類均爲 HashMap 中的靜態內部類,一個爲鏈表結構時的數據存儲對象,一個爲紅黑樹結構時的數據存儲對象.不理解這兩個內部類的原理對其他含有這兩個類的方法的理解就會多些困難.個人建議查看源碼時最好先理解這兩個內部類.

1.Node<K,V>

原 HashMap 內部類名爲 Entry<K,V>,如今改爲 Node<K,V>
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash; // 對key的hashcode值進行hash運算後得到的值,存儲在 Node,避免重複計算
    final K key;    // key唯一
    V value;        // 不唯一
    Node<K,V> next; // 存儲指向下一個 Node 的引用,單鏈表結構引用,防止 key 值不同,hash 值相同.

    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);
    }
    // 同一個key時,新值替換舊值,返回舊值
    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.TreeNode<K,V>

這個內部類含有500行左右代碼,這裏就不逐行展示了.可以自己閱讀下.

 

轉載自 https://www.jianshu.com/p/ee0de4c99f87 ,添加了一些自己的理解。有些詳細過程還不是很清楚,比如擴充時的具體過程,還需要學習。由於水平有限,難免有些錯誤,歡迎指教.

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