HashMap 內的主要數據結構
- 內部類 Node<K,V>(實現了Map.entry接口,存儲key-value的基礎類,鏈表)
- table (Node<K,V> 數組)
基本思路是(后續會做更詳細的解讀):
- 根據key的hash值確定table的index索引
- table的每個index實際上存儲的是Node鏈表,由第1步確定索引后,再循環鏈表如果存在相同key(key.equals)則替換Node的value值,否則鏈表尾添加新Node
構造函數
無參構造函數
threshold 變量為閾值,table大小查過該變量就會觸發擴容(resize)
而 threshold = capacity * loadFactor
public HashMap() {
// 默認擴容因子0.75
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
// table 的初始化推遲到了 map.put(key,value) 的resize()時
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
/** 省略部分代碼 **/
// 初始化table
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
Map參數構造函數
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;// 默認擴容因子0.75
//主要做兩件事情
//1. 利用上一篇文章提到的 求2的冪 的方法,確定HashMap的size
//2. 將 m 內的key-value(即node)逐個復制到該HashMap中
putMapEntries(m, false);
}
初始變量構造函數
public HashMap(int initialCapacity, float loadFactor) {
// 參數的合法性校驗
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
// threshold 為擴容閾值,超過該值即觸發resize
// tableSizeFor即上篇文章介紹的求2的冪方法
this.threshold = tableSizeFor(initialCapacity);
}
// table 的初始化也推遲到了 map.put(key,value) 的resize()時,與無參構造函數不同的是,此處初始化的大小,正是你設置的initialCapacity,而threshold也被重新計算
// 利用threshold做一個存儲的過渡,完美做到了一點都不浪費不啰嗦的優良傳統
newCap = oldThr;
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 阿里開發規約建議使用該 初始化函數,設置初始容量,盡可能的避免map.resize帶來的性能消耗,也提高存儲空間的利用率(用多少聲明多少)
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
小結
綜上可以發現,初始化主要是設置table容量相關值,具體的table大小初始化推遲到了putValue階段,這也就是下篇文章要介紹的重點。