Java容器 | 基於原始碼分析Map集合體系
一、容器之Map集合
集合體系的原始碼中,Map中的HashMap的設計堪稱最經典,涉及資料結構、程式設計思想、雜湊計算等等,在日常開發中對於一些原始碼的思想進行參考借鑑還是很有必要的。
- 基礎:元素增查刪、容器資訊;
- 進階:儲存結構、容量、雜湊;
API體系
在整個Map和Set的API體系中,最重要的就是HashMap的實現原理:
- HashMap:基於雜湊表管理元素;
- LinkedHashMap:基於HashMap和雙向連結串列;
- HashSet:底層維護HashMap結構;
- LinkedHashSet:繼承HashSet,雙向連結串列;
所以Map和Set的系列中,除特殊API之外,基本原理都依賴HashMap,只是在各自具體實現時,適用於不同特點的元素管理。
二、資料結構
在看HashMap之前,先理解一種資料結構:陣列+連結串列的結構。
基於陣列管理元素的位置,元素的儲存形成連結串列結構,既然是連結串列那麼就可以是單雙向的結構,這需要針對具體的API去分析,透過這個結構可以得到幾個關鍵資訊:
- 擴容:基於陣列則面對擴容問題;
- 連結串列:形成連結串列結構的機制;
- 雜湊:雜湊值計算與衝突處理;
三、HashMap詳解
1、結構封裝
既然上面簡單描述了陣列+連結串列的結構,那麼從原始碼角度看看是如何封裝的:
transient Node[] table;
在HashMap中陣列結構的變數命名為table(表),並且是基於Node
的節點:
static class NodeK,V> implements Map.EntryK,V> {
final int hash;
final K key;
V value;
NodeK,V> next;
}
實現Map.Entry
介面,並定義節點的結構變數,和節點自身的相關方法。
2、構造方法
在知道HashMap中的基礎結構後,可以看其相關的構造方法,初始化哪些變數:
無參構造
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
- float DEFAULT_LOAD_FACTOR = 0.75f;
- this.loadFactor = DEFAULT_LOAD_FACTOR;
實際上還要關注一個核心引數:
static final int DEFAULT_INITIAL_CAPACITY = 1
即陣列預設的初始化容量DEFAULT_INITIAL_CAPACITY
為16,擴容的閾值loadFactor
為0.75,即表示當陣列中元素達到12個便會進行擴容操作。
有參構造
當然也可以透過有參構造方法去設定兩個引數:即容量和擴容的閾值:
public HashMap(int initialCapacity, float loadFactor) ;
透過兩個構造方法的原始碼可知:當直接建立新的HashMap的時候,不會立即對雜湊陣列進行初始化,但是可以對關鍵變數做自定義設定。
3、裝載元素
順著HashMap的使用方法,看元素新增:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
在put的時候並沒有做過多直接操作,而是呼叫兩個關鍵方法:
- hash():計算key的hash值;
- putVal():元素新增過程;
這裡必須看一個關鍵方法,雜湊值的計算:
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時,會使用紅黑樹結構代替連結串列來處理雜湊衝突的查詢效能問題,關於樹結構可以移步之前的相關文章。
4、自動化擴容
容器在一定邊界內可以不斷新增元素,其核心的機制就是擴容,HashMap的擴容遵循最小可用原則,當然容量到達閾值,便會觸發自動擴容機制。
閾值:threshold=capacity*loadFactor,預設即 16*0.75=12
。
核心方法:resize;
核心步驟總結:
- 判斷擴容的邊界引數:threshold;
- 核心引數計算:容量和閾值;
- 基於新引數建立一個新的空陣列;
- 原陣列為null則過程可以理解為初始化;
- 原陣列不為null則擴容並遷移資料;
很顯然如果涉及陣列擴容則會很影響效率,所以在日常開發中,可以在使用HashMap的時候預先估計好HashMap的大小,保證閾值大於儲存的元素數量,儘可能避免進行多次擴容操作。
5、查詢元素
getNode
查詢方法,透過hash值的計算,然後依次經過陣列、紅黑樹、連結串列進行遍歷查詢:
6、刪除元素
removeNode
刪除方法,首先透過hash值的計算,找到要刪除的節點,然後判斷索引位置是紅黑樹還是連結串列結構,分別執行各自的刪除流程:
7、補充說明
這裡對兩個方法做個簡單的說明:hashCode()
與equals()
,通常來說重寫equals方法的時候需要重寫hashCode方法。
這兩個方法都可以用來比較兩個物件是否相等,但是hash值有存在衝突的情況,可能存在兩個物件的hash值衝突,這時候可以透過equals判斷物件值是否相同,==
判斷值物件,地址判斷引用物件。
在HashMap的結構中,連結串列上的hash值相同情況還要透過equals方法來判斷具體值是否相同,才能找到相應的物件。
四、原始碼地址
GitHub·地址
GitEE·地址
作者:知了一笑
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4328/viewspace-2807183/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java容器 | 基於原始碼分析List集合體系Java原始碼
- Java 基礎(七)集合原始碼解析 MapJava原始碼
- 詳解Java 容器(第④篇)——容器原始碼分析 - MapJava原始碼
- 基於JDK1.8,Java容器原始碼分析JDKJava原始碼
- 【Java集合】ArrayList原始碼分析Java原始碼
- JAVA集合:ArrayList原始碼分析Java原始碼
- Java 集合包原始碼分析Java原始碼
- 深入Java原始碼解析容器類List、Set、MapJava原始碼
- Java集合原始碼分析(十四):TreeMapJava原始碼
- Java集合原始碼分析(九)——HashSetJava原始碼
- java集合原始碼分析(六):HashMapJava原始碼HashMap
- Java 集合框架------ArrayList原始碼分析Java框架原始碼
- java集合原始碼分析(三):ArrayListJava原始碼
- Java集合體系總結 Set、List、Map、QueueJava
- Java集合原始碼分析之基礎(二):雜湊表Java原始碼
- Java容器原始碼學習--ArrayList原始碼分析Java原始碼
- Java容器類原始碼分析之Iterator與ListIterator迭代器(基於JDK8)Java原始碼JDK
- Java集合乾貨——CopyOnWriteArrayList原始碼分析Java原始碼
- JAVA ArrayList集合底層原始碼分析Java原始碼
- Java集合原始碼分析之開篇Java原始碼
- Java集合乾貨——ArrayList原始碼分析Java原始碼
- Java原始碼分析:Guava之不可變集合ImmutableMap的原始碼分析Java原始碼Guava
- Java容器系列-LinkedList 原始碼分析Java原始碼
- 容器類原始碼解析系列(一) ArrayList 原始碼分析——基於最新Android9.0原始碼原始碼Android
- Map集合(Java基礎、skycto JEEditor)Java
- ConcurrentLinkedQueue 原始碼分析 (基於Java 8)原始碼Java
- Java集合中List,Set以及Map等集合體系詳解(史上最全)Java
- 詳解Java 容器(第③篇)——容器原始碼分析 - ListJava原始碼
- Java容器類框架分析(5)HashSet原始碼分析Java框架原始碼
- Java容器類框架分析(1)ArrayList原始碼分析Java框架原始碼
- 詳解Java 容器(第⑤篇)——容器原始碼分析 - 併發容器Java原始碼
- 死磕 java集合之ArrayList原始碼分析Java原始碼
- 死磕 java集合之HashMap原始碼分析JavaHashMap原始碼
- 死磕 java集合之CopyOnWriteArrayList原始碼分析Java原始碼
- 死磕 java集合之PriorityQueue原始碼分析Java原始碼
- 死磕 java集合之TreeSet原始碼分析Java原始碼
- 死磕 java集合之WeakHashMap原始碼分析JavaHashMap原始碼
- Java 集合系列之 LinkedList原始碼分析Java原始碼