集合體系的源碼中,Map中的HashMap的設計堪稱最經典,涉及數據結構、編程思想、哈希計算等等,在平常開發中對於一些源碼的思想進行參考借鑑仍是頗有必要的。java
API體系git
在整個Map和Set的API體系中,最重要的就是HashMap的實現原理:github
因此Map和Set的系列中,除特殊API以外,基本原理都依賴HashMap,只是在各自具體實現時,適用於不一樣特色的元素管理。算法
在看HashMap以前,先理解一種數據結構:數組+鏈表的結構。數據庫
基於數組管理元素的位置,元素的存儲造成鏈表結構,既然是鏈表那麼就能夠是單雙向的結構,這須要針對具體的API去分析,經過這個結構能夠獲得幾個關鍵信息:編程
既然上面簡單描述了數組+鏈表的結構,那麼從源碼角度看看是如何封裝的:segmentfault
transient Node<K,V>[] table;
在HashMap中數組結構的變量命名爲table(表),而且是基於Node<K,V>
的節點:設計模式
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; }
實現Map.Entry
接口,並定義節點的結構變量,和節點自身的相關方法。數組
在知道HashMap中的基礎結構後,能夠看其相關的構造方法,初始化哪些變量:數據結構
無參構造
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; }
實際上還要關注一個核心參數:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16
即數組默認的初始化容量DEFAULT_INITIAL_CAPACITY
爲16,擴容的閾值loadFactor
爲0.75,即表示當數組中元素達到12個便會進行擴容操做。
有參構造
固然也能夠經過有參構造方法去設置兩個參數:即容量和擴容的閾值:
public HashMap(int initialCapacity, float loadFactor) ;
經過兩個構造方法的源碼可知:當直接建立新的HashMap的時候,不會當即對哈希數組進行初始化,可是能夠對關鍵變量作自定義設置。
順着HashMap的使用方法,看元素添加:
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
在put的時候並無作過多直接操做,而是調用兩個關鍵方法:
這裏必須看一個關鍵方法,哈希值的計算:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
並非直接獲取Object中hashCode的返回值,計算key對應的hashCode值,和hashCode值右移16位的值,並對兩個結果進行異或運算,以此拉低哈希衝突發生的機率。
再看putVal()
方法,這裏的操做就至關精彩:
核心步驟總結:
這裏還須要說明一個問題:
HashMap基於紅黑樹來處理哈希衝突問題,若是hash衝突過多,對O(n)的查詢性能的影響很是大,當衝突節點鏈表的衝突元素數量到達8時,而且數組的長度到達64時,會使用紅黑樹結構代替鏈表來處理哈希衝突的查詢性能問題,關於樹結構能夠移步以前的相關文章。
容器在必定邊界內能夠不斷添加元素,其核心的機制就是擴容,HashMap的擴容遵循最小可用原則,固然容量到達閾值,便會觸發自動擴容機制。
閾值:threshold=capacity*loadFactor,默認即 16*0.75=12
。
核心方法:resize;
核心步驟總結:
很顯然若是涉及數組擴容則會很影響效率,因此在平常開發中,能夠在使用HashMap的時候預先估計好HashMap的大小,保證閾值大於存儲的元素數量,儘量避免進行屢次擴容操做。
getNode
查找方法,經過hash值的計算,而後依次通過數組、紅黑樹、鏈表進行遍歷查詢:
removeNode
刪除方法,首先經過hash值的計算,找到要刪除的節點,而後判斷索引位置是紅黑樹仍是鏈表結構,分別執行各自的刪除流程:
這裏對兩個方法作個簡單的說明:hashCode()
與equals()
,一般來講重寫equals方法的時候須要重寫hashCode方法。
這兩個方法均可以用來比較兩個對象是否相等,可是hash值有存在衝突的狀況,可能存在兩個對象的hash值衝突,這時候能夠經過equals判斷對象值是否相同,==
判斷值對象,地址判斷引用對象。
在HashMap的結構中,鏈表上的hash值相同狀況還要經過equals方法來判斷具體值是否相同,才能找到相應的對象。
GitHub·地址 https://github.com/cicadasmile/java-base-parent GitEE·地址 https://gitee.com/cicadasmile/java-base-parent
閱讀標籤
【Java基礎】【設計模式】【結構與算法】【Linux系統】【數據庫】
【分佈式架構】【微服務】【大數據組件】【SpringBoot進階】【Spring&Boot基礎】