HashMap原始碼分析解析
阿新 • • 發佈:2021-02-09
HashMap原始碼解析-更新中...
雖盡全力認真學習解讀,但是難免還是會有疏漏,請各位大佬蒞臨指導,指出錯誤,必定認真修改。及時更新,感謝感謝!!
1、屬性解析
1.1 hash初始長度
- 最主要還是Hash表的內容,預設初始長度為16,擴容需為2的冪,也就是左移一位
/**
* The default initial capacity - MUST be a power of two.
* 這是預設的容量,必須為2的倍數,目前預設為 2^4 = 16
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
1.2 負載擴容的因子
- 為啥預設的擴充套件因子為0.75,說法眾說紛紜
- 其中一個如果為0.5,會導致hash表的內容過大,如果為1的話,會導致重複的hash較多,達不到快速讀取的效果
- 我個人的看法,16*0.75 = 12 , 16(右移)擴容一倍為32,12右移一位為24
實際計算,最佳的擴充套件因子等於0.6931 1 ,根據上述情況,0.75很方便計算。
/**
* The load factor used when none specified in constructor.
* 預設讀取的負載 = 0.75,容量充滿75%以及以上是,進行擴容,擴容一倍
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The load factor for the hash table.
* hash表的載入擴充係數
*/
final float loadFactor;
1.3 單鏈表和二叉樹轉換
- java8,hash地址碰撞之後,使用的鏈地址法。
轉換的過程,當單鏈表的長度大於等8時,會轉換為紅黑樹進行儲存(ps:實際程式碼跟蹤下來,其實雙鏈表和紅黑樹共存),當具體的值小於6時,又會拆成單鏈表。
/**
* jdk1.8中,hash衝突時,資料內容放在連結串列中,當連結串列的長度超過8時,則轉化為紅黑樹
* 連結串列和紅黑樹的轉換閾值,最小應該為2,至少應該等於8
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 修改紅黑樹為連結串列的閾值,
*/
static final int UNTREEIFY_THRESHOLD = 6;
1.4 容量最大值和閾值最大值
- 容量預設最大值
/**
* 最大的容量,小於等於2^30
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
- 最大的閾值
/**
* The next size value at which to resize (capacity * load factor).
* 下一次擴容的閾值 (當前容量 * 擴充係數loadFactor)
*/
int threshold;
......
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
1.5 計算hash值,如何對映
- 計算hash值
/**
* 大致的意思,計劃hash內容變得比之前簡化,高16位和低16位進行異或操作獲取hash值,對於容易出現衝突的一點,使用連結串列和紅黑樹進行處理
>> 帶符號右移 >>> 無符號右移,高位全部補0
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
......
static class Node<K,V> implements Map.Entry<K,V> {
......
// 獲取當前的hash的值
public final int hashCode() {
// key值得hashCode 異或(相同為0不同為1) 值的hashCode
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
......
}
- 如何進行對映
- tab指 hashTable的列表
- n只當前容量大小
tab[(n - 1) & hash]
1.6 hash擴容
具體程式碼提示,在下文中展示
- 針對原始的連結串列內容
- 待完成
- 針對連結串列和紅黑樹
- 連結串列:根據新的容量 (n)&hashCode>0,擴容的部分,hash有無高位,無高位歸原始hash值連結串列,有高位歸屬新的hash值連結串列
- 紅黑樹: 根據新的容量 (n)&hashCode>0,擴容的部分,hash有無高位,之後判斷是否達到維持紅黑樹的最小長度,再判斷無高位歸屬原始hash,是轉換為連結串列還是保持紅黑樹。高位的更新hash其餘一致。
1.7 支援不支援null值
null的hash值是0,預設在hash表的第一個
2、實現的介面&繼承類的解析
3、程式碼分段解析
3.1 儲存節點解析
/**
* hash表的內容,擴容必須為2的冪
*/
transient Node<K,V>[] table;
......
// 初始化table列表
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
......
/**
* 基本的節點
*/
static class Node<K,V> implements Map.Entry<K,V> {
// hash值是不能改變的,否則下一次就找不到這個值了
final int hash;
// 儲存的鍵,鍵也不能更改,一般建議為Int或者String
final K key;
// 儲存的物件
V value;
// 連結串列的下一個指標
Node<K,V> next;
// 初始化
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
// 獲取當前的hash的值
public final int hashCode() {
// key值得hashCode 異或(相同為0不同為1) 值的hashCode
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
// 設定新值並返回舊值
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
// 內容對比,要麼引用物件相等,要麼型別相同,並且鍵值相等
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
3.2 紅黑樹節點解析
4、演算法解析
參考文獻
注:原始碼部分,來自java原始碼2,註釋是自己寫的,刪除或者部分刪除原來的英文註釋
java8原始碼 ↩︎