主要過一遍HashMap中的常量、構造方法、put方法
當我們呼叫put時,實際上就是呼叫putVal
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
putVal
/**
* @param onlyIfAbsent 若為true,插入重復key的值時,不改變已存在的value
* @param evict 若為false,則表為創建模式(在HashMap中因該沒啥用,LinkedhashMap需要注意)
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab;
Node<K,V> p; // p在后邊用來暫存沖突位置上的元素
int n, i; // n為table.length
// 1. 新創建的HashMap先初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 2. 若指定位置不存在元素(沒有hash沖突)
if ((p = tab[i = (n - 1) & hash]) == null) // n為hashMap的長度,即2的冪次,故:(n-1)&hash = hash%n
tab[i] = newNode(hash, key, value, null);
else {
// 3. 發生沖突時處理邏輯
Node<K,V> e;
K k; // 沖突位置上的key
// 3.1 key相同則覆寫舊值
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 3.2 已有很多沖突值(已經轉為樹了),則put進該沖突樹
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 3.3 拉鏈法存放
else {
// 3.3.1 回圈沖突鏈表,將新來的沖突的元素尾插到鏈表中
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // TREEIFY_THRESHOLD = 8
// 鏈表轉紅黑樹
treeifyBin(tab, hash);
break;
}
// 如果新插入的元素是與沖突鏈表上的key相同的,那么就要覆寫這個key對應的value
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
// beark后走【a】處的邏輯
break;
p = e;
}
}
// 回圈結束后,經歷尾插后的e為空
if (e != null) { // 【a】existing mapping for key
V oldValue = https://www.cnblogs.com/daydreamer-fs/archive/2022/09/25/e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);// 這方法空的,沒啥用,在LinkedHashMap中被重寫
return oldValue;
}
}
++modCount; // 記錄被結構修改的次數
// 如果當前保存的k-v數量(不包含沖突的)大于閾值 則擴容
if (++size > threshold)
resize();
afterNodeInsertion(evict);// 這方法空的,沒啥用,在LinkedHashMap中被重寫
return null;
}
resize
初始化或加倍表的大小,
如果為空,則按照欄位閾值中持有的初始容量目標分配,
否則,因為我們使用的是2的冪展開,所以每個bin中的元素要么必須保持相同的索引,要么在新表中以2的冪偏移量移動,
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold; // 類成員變數threshold,int型別默認值為0
int newCap, newThr = 0;
// 1.確定新閾值和新容量
// 1.1 元素不為空
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) { // MAXIMUM_CAPACITY = 1 << 30 = 1073741824
threshold = Integer.MAX_VALUE;// MAX_VALUE = https://www.cnblogs.com/daydreamer-fs/archive/2022/09/25/2147483647
return oldTab;
}
// newCap擴為原來的2倍 ,若其在[16,1073741824)范圍內threshold也翻倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
}
// 1.2 元素為空,且閾值有效(可能是呼叫HashMap(int initialCapacity,float loadFactor)初始化或被清空元素的hashmap)
else if (oldThr > 0)
newCap = oldThr;
// 1.3 新創建的HashMap 容量默認為16,閾值默認為(負載因子*容量)0.75*16=12
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 2.擴容
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 回圈老的元素
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 2.1 該元素鏈表中沒有存盤沖突值,重新計算該元素存盤位置,并賦值
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 2.2 如果該元素保存到沖突值很多 以至于鏈表已經轉成紅了
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 2.3 有產生沖突的值,但還沒有轉成樹
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// 空鍵的hash為零,或出現1001 & 0110這種情況
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;
}
常量及成員變數
// 默認的初始容量 16(必須是2的冪)
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 在建構式中沒有指定時使用的負載因子,
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 在第一次使用時初始化,并根據需要調整大小
// 長度總是2的冪
transient Node<K,V>[] table;
構造
// 構造一個具有默認初始容量(16)和默認負載因子(0.75)的空HashMap,
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
put
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
Hash
// 計算 key.hashCode() 并將哈希的較高位傳播(XOR)到較低位, 由于該表使用二次冪掩碼,因此僅在當前掩碼之上位變化的散列集將始終發生沖突, (已知的例子是在小表中保存連續整數的 Float 鍵集,)因此,我們應用了一種變換,將高位的影響向下傳播, 在位擴展的速度、實用性和質量之間存在折衷, 因為許多常見的散列集已經合理分布(所以不要從傳播中受益),并且因為我們使用樹來處理 bin 中的大量沖突,我們只是以最便宜的方式對一些移位的位進行異或,以減少系統損失, 以及合并最高位的影響,否則由于表邊界,這些最高位將永遠不會用于索引計算,
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/509484.html
標籤:其他