Map:探尋HashMap及其實現原理

今天看下util包下的HashMap類,看看我們常用的數據結構有哪些好用方法沒被get到!

類實現了一個抽象類和一堆接口,先從接口看起

這個接口沒有定義要實現的方法,設計思路應該是遵循 "接口隔離原則" 實現最小範圍調用

接下來看看Map接口,所有的內容如下

方法總結如下方表格中彙總所述:

method parameterType resultType remark
size()   int 獲取Map大小
isEmpty()   boolean 是否爲空,爲空返回true

containsKey()

Object boolean Map是否包含某個key
containsValue() Object boolean Map是否包含某個Value
get() Object V 獲取指定Key對應的Value,沒有返回null
put() K,V V 添加鍵值對
remove() Object V 刪除指定key對應的鍵值對
putAll() Map<? extends K,V extends V>   將另一個Map對象添加到當前Map
clear()     清空Map
KeySet()   Set<K> 獲取Map的所有Key並存在Set中
Values()   Collection<V> 獲取所有的value
entrySet()   Set<Entry<K,V>> 獲取Map下所有的Entry
Entry<K,V>     結構,抽象類
equals() Object boolean  
hashCode()   int  
getOrDefault() Object,V V  
forEach() BiConsume<? super K,? super V>    
replaceAll() BiConsume<? super K,? super V>, ? extends V    
putIfAbsent() K,V V  
remove() Object,Object boolean  
replace() K,V,V boolean  
replace() K,V V  
computeIfAbsent() K,Function<? super K ,? extends V> V  
computeIfPresent() K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction V  
compute() K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction V  
merge() K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction V  

 

對K,V類型不太瞭解的話可以看下這篇博客《Java泛型中E、T、K、V等的含義》

遵守如上規則既是一個合格的Map,但今天是解析HashMap類的,so接着 看看HashMap類唯一繼承AbstractMap

這個類是Map接口的實現類,主要作用是將Map接口下 相同的 具體實現方法實現彙總共享,起到承上啓下的作用。

其中核心方法 entrySet() 需要注意, 這個抽象方法需要子類實現 並 返回子類所有Entry實例,提供抽象類其他方法使用

背景介紹完了,現在看看HashMap類

HashMap繼承AbstractMap抽象類,隱式的引入了Map接口,故在AbstractMap中未實例化的方法將會在HashMap中實現,在AbstractMap定義的抽象方法也會在這裏得以實現,另包含了一些HashMap的功能處理方法,所以看起來有點多

方法和成員變量太多博主也不想一個一個看了,結合實際內容 和 本文命題 探尋一下重要部分 並 掃掃盲

在博主的理解中HashMap的組成分爲如下部分:

1>核心結構

2>初始化構造器

3>針對核心結構進行業務運用

1 先來認識HashMap的核心結構

Map.Entry -> AbstractMap中Map.Entry實現 -> HashMap中Map.Entry實現

1>Node.class [implements Map.Entry<K,V>]

這個Node對象類似於Integer對象中封裝的int一樣,在Integer中都是圍繞int做處理,在HashMap中也是相同的原理

Node對象結構和提供的能力如上,比較簡潔,其中Node<K,V> next 成員變量就是鏈表結構的組成依據

 

2>TreeNode.class [extends LinkedHashMap.Entry<K,V>][LinkedHashMap.Entry<K,V> extends HashMap.Node<K,V>]

詳解請看這篇博文【博文在講解執行過程中有點難理解的地方可以畫圖描述步驟方便理解】

 

2.看看在初始化HashMap時候進行了那些默認配置處理

構造器中初始化了一些配置,可以看下《HashMap中capacity、loadFactor、threshold、size等概念的解釋》這篇文章

默認構造器

帶參構造器(int)

帶參構造器(int,float)

           按照文章中所描述的內容我們知道這些參數是爲HashMap自動擴容做準備

帶參構造器(Map<? extends K,? extends V>)

3.運用部分

因爲根據HashMap進行業務運用的方法太多,現在依據比較熱點的問題對運用這塊進行剖析

1:HashMap的存儲(put的工作)原理是?

其中用到位運算符,不熟的同學請先了解

根據代碼一步一步解析:

1> 如果爲空,通過resize方法初始化HashMap的成員變量node數組容量

final Node<K,V>[] resize() {
	// 獲取當前HashMap中保存的node數組
        Node<K,V>[] oldTab = table;
	// 判斷數組是否爲null,如果爲空,舊桶的數量爲0,否則得到桶的數量
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
	// 獲取舊的(reside方法判斷執行依據數據)
        int oldThr = threshold;
	// 創建新的桶和新的(reside方法判斷執行依據數據)
        int newCap, newThr = 0;
	// 如果舊桶的數量大於0
        if (oldCap > 0) {
	// 如果舊桶的數量大於等於最大的桶數
            if (oldCap >= MAXIMUM_CAPACITY) {
	// 當前HashMap觸發(reside方法判斷執行依據數據)等於Integer的最大值
                threshold = Integer.MAX_VALUE;
	// 返回當前node數組給調用方法
                return oldTab;
            }
	// 否則新的桶數量賦值爲舊的桶數量的一倍,如果新的桶小於桶的最大數 並且 舊桶大於默認桶的個數(16)
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
	// 新桶的個數是舊桶的一倍
                newThr = oldThr << 1; // double threshold
        }
	// 在舊桶的數量不大於0情況,判斷HashMap的(reside方法判斷執行依據數據)是否大於0
        else if (oldThr > 0) // initial capacity was placed in threshold
	// 條件成立 舊桶的數量等同於(reside方法判斷執行依據數據)
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
	// 否則新桶的數量爲默認桶的數量(16)
            newCap = DEFAULT_INITIAL_CAPACITY;
	// 新HashMap的擴容判斷數據爲 裝載因子* 默認桶數  0.75 * 16 = 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
	// 如果新桶的(reside方法判斷執行依據數據) == 0 
        if (newThr == 0) {
	// 重新獲取一遍當前(reside方法判斷執行依據數據)
            float ft = (float)newCap * loadFactor;
	// 如果新桶小於最大桶數並且 (reside方法判斷執行依據數據) 小於最大桶數,就用新的(reside方法判斷執行依據數據),否則(reside方法判斷執行依據數據)爲Integer的最大值
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
	// 當前HashMap的(reside方法判斷執行依據數據)爲新的  (reside方法判斷執行依據數據)
        threshold = newThr;
	// 創建新的node數組,大小爲新的桶數
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
	// 當前HashMap的node數組等於新的node數組
        table = newTab;
	// 如果舊的node數組不爲空的話
        if (oldTab != null) {
	// 循環拿到舊的node數組中node實例
            for (int j = 0; j < oldCap; ++j) {
	// 創建一個node對象
                Node<K,V> e;
	// 將從node數組中拿到的node實例賦值給新建的node對象,並判斷是否爲null
                if ((e = oldTab[j]) != null) {
		// 如果不爲null,將node數組中的對應下標node賦值爲null
                    oldTab[j] = null;
		// 如果node沒有鏈表的下一個節點
                    if (e.next == null)
		// 將得到的node存入node數組 (與計算)位置
                        newTab[e.hash & (newCap - 1)] = e;
		// 如果node實現了treeNode,既是樹存儲結構
                    else if (e instanceof TreeNode)
		// 則調用treeNode中的split方法實現存儲
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
		// 當node不爲null的時候,又不是樹存儲結構,此時的node是一個鏈表結構
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
		// 賦值next爲node對象的下一個node對象
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

2> 當下標沒中沒有node對象時候直接創建一個node對象進去,否則進入else分支,其中難點解釋一下

if ((p = tab[i = (n - 1) & hash]) == null)

// 其中hash是根據Key從靜態方法hash()中獲取

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//獲得的hashcode是一個32位的int型數據,很明顯從2的-31次冪到2的32次冪長度的數據遠超過hashmap的
//最大容量,所以這裏通過key的hashcode右移16位與原hashcode進行或非運算,實際上就是hashcode與
//前16位換成0的hashcode進行或非運算,如果高位不參與運算,發生hash衝撞的機率就會增大,從而影響性能


// 至於爲什麼用n-1,舉個例子,假設n爲16,n-1爲15,
// 也就是0000 0000 0000 0000 0000 0000 0000 1111,和hash值進行與運算,
// 也就是這後四位進行運算,
// 結果就被限制在0000~1111之間,正好是數組的長度,遊標從0到15

以上內容來自
--------------------- 
作者:li_cangqiong 
來源:CSDN 
原文:https://blog.csdn.net/li_cangqiong/article/details/81871332 

3> 如果(數組下標的node元素)hash等於新存入的Hash,並且key相同,則進行替換

4> (數組下標的node元素)是 紅黑樹  結構,則通過putTreeVal()方法執行添加操作【TreeNode中引入的文章中有具體解釋】

5> 否則(數組下標的node元素)是鏈表結構,因爲拿到的是一個鏈表,所以這裏有個 if( (e=p.next) == null ) 判斷,爲的就是得到 bin 的個數,如果大於8【TREEIFY_THRESHOLD】個則將 bin組 轉換成 紅黑樹

此時遺留一個問題 : 如果 bin組 中也有相同的key,該怎麼處理?下面的判斷則是將這個問題繼續往後拋,接着往下看!

6> 此時我們可以看到,上述遺留問題也迎刃而解,並且得到舊的value值,用來返回給調用者

7> 對一些內容進行修改,其中包括擴容

 

2:HashMap的獲取(get的工作)原理是?

似乎沒什麼好講解的....

最難的部分應該已經講解了,剩下的其他方法只是對這塊的運用,自己看看源碼即可。

那麼第一階段HashMap的探索就到此結束,後續會補充一些HashMap相關的內容:可能是在下次複習HashMap的時候

那麼,就這樣吧

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