HashMap的源碼初步理解

原文鏈接:https://www.jianshu.com/p/ee0de4c99f87

一 定義

基於哈希表的 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;

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);
    }

四 其他函數

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;
    }

 

轉載自 https://www.jianshu.com/p/ee0de4c99f87 ,添加了一些自己的理解。有些詳細過程還不是很清楚,比如擴充時的具體過程,還需要學習。

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