提要:
- put(K key, V value)
- hash(Object key)
- putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
- resize()
- get(Object key)
- getNode(int hash, Object key)
- remove()
- removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)
- replace(K key, V oldValue, V newValue)
詳解:
/**
1.put(K key, V value) 方法:
作用:內部調用了putVal()方法,對指定key的節點進行增改
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
將指定值與該map中的指定鍵相關聯。 如果該map先前包含該鍵的map,則將替換舊值。
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
2.hash(Object key) 方法
參數:Object key,key對象。
作用:俗稱“擾動函數”,讓hashCode的高16位也參與路由運算(若不做此操作,則在路由計算時:(n-1) & hashCode,當n的二進制數小於16位時,那麼h的高16位將與0求與,特徵將被短路掉,無法參與路由運算。)
對key進行轉換,若key爲null,則返回0;否則返回一個“特殊值”。特殊值爲key的hashCode 異或 key的hashCode無符號右移16位的結果值。
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower. Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.) So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
譯文:計算key.hashCode()並將哈希的較高位(XOR)擴展爲較低。 因爲該表使用2的冪次掩碼,所以僅在當前掩碼上方的位中變化的哈希集將始終發生衝突。 (衆所周知的示例是在小表中包含連續整數的Float鍵集。)因此,我們應用了一種變換,將向下傳播較高位的影響。 在速度,實用性和位擴展質量之間需要權衡。 由於許多常見的哈希集已經合理分佈(因此無法從擴展中受益),並且由於我們使用樹來處理容器中的大量衝突集,因此我們僅以最便宜的方式對一些移位後的位進行XOR,以減少系統損失, 以及合併最高位的影響,否則由於表範圍的限制,這些位將永遠不會在索引計算中使用。
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
3.putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) 方法
作用:向hashMap中添加元素或修改值
* Implements Map.put and related methods
*
* @param hash hash for key :key的擾動結果
* @param key the key :要插入的key
* @param value the value to put:要插入的value
* @param onlyIfAbsent if true, don't change existing value:如果是true,表示不改變已存在的值。
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
//tab:表示hashMap的散列表
//p:表示當前散列表的一個元素
//n:表示散列表數組的長度
//i:表示路由尋址結果
Node<K,V>[] tab;
Node<K,V> p;
int n, i;
//延遲初始化 邏輯。第一次調用putVal()時進行初始化hashMap中的最耗費內存的散列表
if ((tab = table) == null || (n = tab.length) == 0)//對n進行賦值
n = (tab = resize()).length;
//路由算法:(n - 1) & hash
if ((p = tab[i = (n - 1) & hash]) == null)//當該桶位爲空
tab[i] = newNode(hash, key, value, null);//則新建一個節點存入數據
else {
Node<K,V> e;//一個臨時節點
K k;//一個臨時key
//當該桶位有元素,且該元素與當前待插入的數據的key相同,則將其賦值給e。後續需要對值進行替換。
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//當該桶位爲一個樹的節點
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//當該桶位爲一個鏈表
for (int binCount = 0; ; ++binCount) {
//如果該元素爲尾節點,將待插入的數據放入新建的node中,放入鏈表尾部。
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果當前鏈表的長度大於等於 樹化閥值 ,那麼就需要進行樹化,將當前鏈表轉化爲紅黑樹
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//當找到了一個與待插入數據的key相同的節點,需要進行替換操作
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//當e不等於null,說明找到了一個與待插入元素的key完全相同的數據,進行替換即可
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//表示散列表結構的修改次數。增刪次數。
++modCount;
//size:散列表元素數。如果該值大於擴容閾值,則進行擴容。
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
/**
4.resize()方法
作用:初始化 和 擴容
實現流程:
1.計算出擴容後的 數組大小 和 擴容閾值
2.進行擴容
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*譯文:初始化或增加表大小。 如果爲空,則根據字段閾值中保持的初始容量目標進行分配。 否則,因爲我們使用的是2的冪,所以每個bin中的元素必須保持相同的索引,或者在新表中以2的冪偏移。
* @return the table
*/
final Node<K,V>[] resize() {
//引用擴容前的哈希表
Node<K,V>[] oldTab = table;
//擴容前的數組長度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//擴容前的 擴容閾值,即觸發本次擴容的閾值
int oldThr = threshold;
//擴容之後的 數組長度和擴容閾值
int newCap, newThr = 0;
//條件成立說明hashMap中的散列表已經初始化過了,是一次正常的擴容
if (oldCap > 0) {
//擴容之前的table數組大小已經達到最大閾值後,則不擴容,且設置擴容條件爲int的最大值。(隱含:再也不會擴容)
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//oldCap左移一位數值翻倍,並且賦值給newCap。newCap小於數組最大值限制 且擴容之前的閾值 >= 16
//這種情況下,則下次擴容的閾值 等於當前閾值的兩倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//oldCap == 0.說明hashMap中的散列表是null
//1.new hashMap(initCap, loadFactor)
//2.new hashMap(initCap)
//3.new hashMap(map);且這個map有數據
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//oldCap == 0.oldThr == 0
//new HashMap();
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;//16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//負載因子 * 數組長度 = 12
}
//newThr爲0時,通過newCap和LoadFactor計算出一個newThr
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"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//hashMap本次擴容之前table不爲null。即oldTab已經指向了一個數組。(數組中有無數據尚未可知)
if (oldTab != null) {
//遍歷老 table
for (int j = 0; j < oldCap; ++j) {
//定義一個臨時節點
Node<K,V> e;
//說明當前桶位有數據。可能爲 單個數據,鏈表,紅黑樹。
if ((e = oldTab[j]) != null) {
//方便JVM GC回收內存
oldTab[j] = null;
//情況一:表明 e是一個單獨的元素
if (e.next == null)
//通過 路由算法 得到 當前桶位元素在新數組中的位置,並放入。
newTab[e.hash & (newCap - 1)] = e;
//情況二:如果該元素是一個樹節點
else if (e instanceof TreeNode)
//
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//情況三:如果該元素是一個鏈表節點
else { // preserve order
//桶位已經形成鏈表
//低位鏈表:存放在擴容之後的數組的下標位置,與當前數組的下標位置一致(原來鏈表的高位爲0的元素放在這裏)
Node<K,V> loHead = null, loTail = null;
//高位鏈表:存放在擴容之後的數組的下標位置,爲當前數組下標位置加上擴容前的數組長度。(原來鏈表的高位爲1的元素放在這裏)
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
//假如是在13這個位置,
//hash-> ... 1 1101
//hash-> ... 0 1101
// 0b 1 0000 ------>
//實現了 當第5位爲 0 則放在低位鏈表中。
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);
//當低位鏈表的中有數據,低位鏈表初始化尾節點(切斷尾節點與其原next節點的聯繫)。頭結點需要放入數組對應的桶位。
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
//當高位鏈表的中有數據,高位鏈表初始化尾節點(切斷尾節點與其原next節點的聯繫)。頭結點需要放入數組對應的桶位。
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
/**
5.get(Object key)方法
作用:根據key獲取hashMap中存儲的數據
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*譯文:返回指定鍵所映射到的值;如果此映射不包含鍵的映射關係,則返回{@code null}。
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
*譯文:<p>更正式地說,如果此映射包含從鍵{@code k}到值{@code v}的映射,使得{@code(key == null?k == null:key.equals(k) )},則此方法返回{@code v}; 否則返回{@code null}。 (最多可以有一個這樣的映射。)
* <p>A return value of {@code null} does not <i>necessarily</i>
* indicate that the map contains no mapping for the key; it's also
* possible that the map explicitly maps the key to {@code null}.
* The {@link #containsKey containsKey} operation may be used to
* distinguish these two cases.
*譯文:<p>返回值{@code null}並不<i>不必要</ i>表示映射不包含該鍵的映射; 映射也可能將鍵顯式映射到{@code null}。 {@link #containsKey containsKey}操作可以用來區分這兩種情況。
* @see #put(Object, Object)
*/
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
6.getNode(int hash, Object key)方法
作用:獲取hashMap中的node中的數據
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
//引用當前hashMap的散列表
Node<K,V>[] tab;
//first:桶位中的頭元素;
//e:臨時node元素
Node<K,V> first, e;
int n; K k;
//當 該hashMap中有數據。
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {//路由尋址 找到該位置的元素,賦值爲頭元素。當它不爲空時。
//第一種情況:當得到的頭元素就是要找的元素,直接返回即可
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))//key != null && key.equals(k) 是爲了比較的更完善一些,先判空是爲了避免後面調用equals()時出現空指針異常。
return first;
//當該節點爲鏈表節點或紅黑樹節點
if ((e = first.next) != null) {
//第二種情況:當該節點是樹節點
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//第三種情況:該節點爲鏈表,遍歷該鏈表,尋找要找的節點
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;//直到找到,
} while ((e = e.next) != null);
}
}
return null;//或找不到
}
/**
7.remove()方法
作用:刪除指定key對應的節點
* Removes the mapping for the specified key from this map if present.
*譯文:此映射中指定鍵的映射(如果存在)。
* @param key key whose mapping is to be removed from the map
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/***
remove(Object key)的同名方法
作用:當key與value都滿足條件才能刪除
*/
@Override
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
/**
8.removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)方法
作用:刪除指定節點
matchValue:當它爲true時,value也需要匹配上。
* Implements Map.remove and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to match if matchValue, else ignored
* @param matchValue if true only remove if value is equal
* @param movable if false do not move other nodes while removing
* @return the node, or null if none
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
//tab:引用當前hashMap的散列表
Node<K,V>[] tab;
//當前的node元素
Node<K,V> p;
//n表示散列表數組的長度,index表示尋址結果
int n, index;
//(tab = table) != null table指向了一個數組,而不是null
//且 該數組的長度大於0
//且 該元素所在的位置 有數據節點 而不是null
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
//node爲查找到的結果,e表示當前node的下一個元素
Node<K,V> node = null, e;
K k; V v;
//情況一:當不用下鑽就找到了 要找的 節點,即該節點爲某個桶位的第一個元素。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//當該節點是鏈表或樹(節點)
else if ((e = p.next) != null) {
//情況二:當該節點是樹節點時
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
//當該節點是鏈表時
else {
//遍歷 鏈表
do {
//情況三:找到要找的元素,賦值給node,跳出循環即可
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
//更新p的指向,可以保留找到鏈表中目標節點前的那個節點。
p = e;
} while ((e = e.next) != null);
}
}
//當node不爲空,即已經按照key查找到數據了,根據matchValue的boolean值判斷是否需要比較value值,當其爲true時需要比較。
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//進入刪除操作
//情況一:當 當前節點是 紅黑樹 節點時
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
//情況二:當 數組中的某個桶位上的第一個元素即爲要 刪的元素
else if (node == p)
//將當前節點 短路(刪除) 掉
tab[index] = node.next;
else
//情況三:當 節點爲鏈表中的一個非頭節點時,將node節點短路掉即可。
p.next = node.next;
//結構修改次數加1
++modCount;
//kv對的數量減1
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
/***
9.replace(K key, V oldValue, V newValue)方法
作用:替換指定key和value的value
*/
@Override
public boolean replace(K key, V oldValue, V newValue) {
Node<K,V> e; V v;
if ((e = getNode(hash(key), key)) != null &&
((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
e.value = newValue;
afterNodeAccess(e);
return true;
}
return false;
}
/***
作用:替換指定key的value
*/
@Override
public V replace(K key, V value) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) != null) {
V oldValue = e.value;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
return null;
}