Java java.util.HashMap實現原理原始碼分析
一、HashMap原始碼概覽
I. 可以結合JDK原始碼包瞭解java.util.HashMap實現原理
II. 繼承抽象類java.util.AbstractMap,實現了三個介面,分別是:java.util.Map、java.lang.Cloneble、 java.io.Serializable
III. 主要的成員變數
編號 | 變數程式碼 | 變數註釋 |
1 | static final int DEFAULT_INITIAL_CAPACITY = 16; | 預設的初始化容量大小為16,可以通過HashMap(int initialCapacity) 或者HashMap(int initialCapacity,float loadFactor)這兩個建構函式來改變此容量大小,但是實際的容量值可能與你設定的值不同(具體操作見下面解釋) |
2 | static final int MAXIMUM_CAPACITY = 1 << 30; | 最大的容量值,1左移30位,即2³⁰ |
3 | static final float DEFAULT_LOAD_FACTOR = 0.75f; | 預設的載入因子(作用見下面解釋) |
4 | transient Entry[] table; | Entry陣列, HashMap內部儲存實際就是Entry物件陣列(Entry詳細解釋見下面解釋,關鍵字transient的作用見我另一部落格說明, 點選開啟連結 ). 此陣列如果需要的話,大小可以被重置,但是其長度要一直是2的冪次方 |
5 | transient int size; | 此Map的大小(即k-v對映的長度) |
6 | int threshold; | Map準備擴容的臨界閥值(臨界閥值的計算方法見下面的解釋),即在Map在使用過程中,如果發現Map使用量(即keys大小)已經超過此閥值,則會呼叫resize()方法進行重置大小(會進行再雜湊,將舊資料交換到擴容後的Map容器中,詳細步驟見下面解釋) |
7 | final float loadFactor; | 載入因子,預設值為此表格中編號3的變數對應的值,即0.75,可以通過構造HashMap(int initialCapacity, float loadFactor) 來改變此值 |
8 | transient volatile int modCount; |
VI.構造方法
i. 預設總共有四個建構函式,歸根到底可以總結只有一個構造,下面將逐一介紹
ii. 構造一:HashMap(int initialCapacity,float loadFactor),原始碼以及分析如下:
/**
* 通過給定的容量值和載入因子來構造一個空的 <tt>HashMap</tt>
* 但是指定的這兩個引數值的範圍是有要求的,具體看下面程式碼段的分析
*
* @param initialCapacity 給定的初始化容量
* @param loadFactor 給定的負載因子
* @throws IllegalArgumentException 如果給定的初始化的容量是負數或者給定的負載因子不是正數
*
*/
public HashMap(int initialCapacity, float loadFactor) {
// 如果給定的初始化容量引數是負數,則會丟擲引數不合法的具體異常資訊
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
/*
* 如果給定的初始化容量大於預設的最大容量值(MAXIMUM_CAPACITY常量見上表格III的編號2變數),
* 則實際容量給預設的最大容量值
*/
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 如果負載因子不是正數,則會丟擲引數不合法的具體異常資訊
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
/*
* 計算實際容量
* 1.容量值大小要求是2的冪次方.
* 2.所以要找一個數字是2的冪次方,且大於等於給定的容量值,則實際的容量可能會跟你設定的容量值不一樣
* 3.理解:則譬如你給定的容量值是14,則實際容量值是 2⁴=16, 如果你給定的容量值是16, 則實際容量值也是 2⁴=16
* */
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
/*
* 1.準備擴容的臨界閥值計算:實際的容量與負載因子的乘積.
* 2.理解:譬如你設定實際容量是16,負載因子是0.75,則當HashMap的使用量大小達到16*0.75=12時,
* 則需要呼叫resize()方法進行擴容,擴容的詳細過程見下面解釋
*/
threshold = (int)(capacity * loadFactor);
// 初始化Map Entry元素陣列
table = new Entry[capacity];
init();
}
iii.構造二:HashMap(int initialCapacity),原始碼以及分析如下:
/**
* 通過給定的容量值和預設的負載因子(0.75)來構造一個空的 <tt>HashMap</tt>
*
* @param initialCapacity 給定的初始容量值
* @throws IllegalArgumentException 如果給定的初始化的容量是負數
*/
public HashMap(int initialCapacity) {
/*
* 可以看出這個建構函式只是指定了初始的容量值,
* 其實呼叫的是構造一HashMap(int initialCapacity,float loadFactor)這個建構函式,
* 只是負載因子用了預設的0.75(預設的負載因子常量DEFAULT_LOAD_FACTOR)
*/
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
vi. 構造三:HashMap(),原始碼以及分析如下:
/**
* 通過預設的初始化容量值(16)以及預設的負載因子(0.75)來構造一個空的 <tt>HashMap</tt>
*/
public HashMap() {
// 預設的負載因子常量
this.loadFactor = DEFAULT_LOAD_FACTOR;
// 通過預設的初始容量以及預設的負載因子來計算出預設的負載閥值(16 * 0.75 = 12)
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
// 預設的初始容量值
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
v. 構造四:HashMap(Map<? extends K, ?extends V> m),原始碼以及分析如下:
/**
*
* 在給定的Map m基礎上來構造一個新的<tt>HashMap</tt>,其中給定的Map m的對映資料會被拷貝到新的HashMap中。
* 新構造的HashMap負載因子使用的是預設的0.75,並且其容量值會根據給定的Map m的大小來重新計算容量,
* 保證新的HashMap容量充足,具體的計算方法參考下面註釋
*
* @param m 給定的基礎Map,資料會被對映到新的HashMap上
* @throws NullPointerException 如果給定的Map是null,則會丟擲空指標異常
*/
public HashMap(Map<? extends K, ? extends V> m) {
/*
*1.可以看出對HashMap的初始化構造還是呼叫上面的構造一HashMap(int initialCapacity,float loadFactor)
*這個建構函式,從而對新建的HashMap進行初始化.
*2.首先是根據指定的Map m的大小來重新計算容量,可以看出新建立的的HashMap的容量大小是取
* max(m的大小/預設的負載因子(0.75)的商, 預設的初始容量16) 的值作為初始化容量
*3.負載因子使用是預設的0.75
*
*/
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
//將給定的Map m的資料拷貝到新建立的HashMap中
putAllForCreate(m);
}
vi. 總結:可以看出上述的構造方法,其實歸根到底可以總結為,在建立HashMap時,需要兩個基本的要素:給定合理的容量、給定合理的負載因子.
二、 HashMap實現具體原理分析
I. 內部儲存如圖:
II. 根據原始碼以及上圖可以分析下述結論
i. HashMap 內部是以可擴容的陣列(Entry[] table)形式來存數資料,每個陣列元素是Entry的資料結構為單元的連結串列
ii. 每個Entry 都要具備基本四個屬性:hash值,key值,value值,其在陣列中下標位置index,如圖
iii. put<K, V>的流程,即新增<K, V>時,HashMap內部的工作流程圖如下:
相關文章
- Java面試題 從原始碼角度分析HashSet實現原理?Java面試題原始碼
- HashMap 實現原理與原始碼分析HashMap原始碼
- HashMap實現原理及原始碼分析HashMap原始碼
- ConcurrentHashMap 實現原理和原始碼分析HashMap原始碼
- 【MyBatis原始碼分析】外掛實現原理MyBatis原始碼
- OpenMP Sections Construct 實現原理以及原始碼分析Struct原始碼
- OpenMP Parallel Construct 實現原理與原始碼分析ParallelStruct原始碼
- OpenMP task construct 實現原理以及原始碼分析Struct原始碼
- Spring原始碼分析之 lazy-init 實現原理Spring原始碼
- redis個人原始碼分析2---dict的實現原理Redis原始碼
- JDK動態代理實現原理詳解(原始碼分析)JDK原始碼
- Java原子類實現原理分析Java
- Java集合類,從原始碼解析底層實現原理Java原始碼
- Promise實現原理(附原始碼)Promise原始碼
- 【原始碼分析】Lottie 實現炫酷動畫背後的原理原始碼動畫
- 原始碼分析 Alibaba sentinel 滑動視窗實現原理(文末附原理圖)原始碼
- HashMap原始碼實現分析HashMap原始碼
- Binder Java層的實現原理分析Java
- Java併發包原始碼學習系列:阻塞佇列BlockingQueue及實現原理分析Java原始碼佇列BloC
- 深入原始碼解析 tapable 實現原理原始碼
- 原始碼|ThreadLocal的實現原理原始碼thread
- GCD原始碼原理分析GC原始碼
- Spring Ioc原始碼分析系列--@Autowired註解的實現原理Spring原始碼
- OpenMP For Construct dynamic 排程方式實現原理和原始碼分析Struct原始碼
- Android 原始碼分析 --Handler 機制的實現與工作原理Android原始碼
- 【Redis】跳躍表原理分析與基本程式碼實現(java)RedisJava
- 深入原始碼,深度解析Java 執行緒池的實現原理原始碼Java執行緒
- Java物件記憶體分配原理及原始碼分析Java物件記憶體原始碼
- OPENMP FOR CONSTRUCT GUIDED 排程方式實現原理和原始碼分析StructGUIIDE原始碼
- OpenMP 執行緒同步 Construct 實現原理以及原始碼分析(上)執行緒Struct原始碼
- OpenMP 執行緒同步 Construct 實現原理以及原始碼分析(下)執行緒Struct原始碼
- Netty原始碼解析 -- PoolSubpage實現原理Netty原始碼
- synchronized實現原理及ReentrantLock原始碼synchronizedReentrantLock原始碼
- Netty原始碼解析 -- PoolChunk實現原理Netty原始碼
- 從原始碼解讀Category實現原理原始碼Go
- InnoDB MVCC實現原理及原始碼解析MVC原始碼
- SpringMVC原始碼分析原理SpringMVC原始碼
- Guava 原始碼分析(Cache 原理)Guava原始碼