HashMap是如何實現的(底層原理)?
阿新 • • 發佈:2019-02-05
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true); //此處引數內hash(key),實際上是獲得key的hashCode值
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {// onlyIfAbsent值為false的時候表示直接覆蓋已存在的key的value值,為true時oldValue為null則才會覆蓋
Node<K, V>[] tab; // 宣告一個tab陣列
Node<K, V> p; // 宣告一個節點Node鍵值對
int n, i; // n表示tab的size; 表示即將把值put到某個Node(key-value)的index
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; // 如果tab為空或者長度為0時,就初始化tab(初始長度為16),並將長度值賦給n
if ((p = tab[i = (n - 1) & hash]) == null ) // 當(n - 1) & hash 位與運算的結果為key對應的index,當為控的時候
tab[i] = newNode(hash, key, value, null); // 則呼叫newNode方法新建一個Node物件賦值給tab[i]
else { // 進入此else的程式碼塊,則表示tab對應index必定已存在Node物件
Node<K, V> e;
K k; // 泛型 物件k 代表key
if (p.hash == hash&& ((k = p.key) == key || (key != null && key.equals(k))))// 當對應index上的Node的hash值與傳入進來的key的hash值相等並且Node的key和傳入的key也相等時則覆蓋value值
e = p;
else if (p instanceof TreeNode) // 當上面不成立的時候,則說明此Node可能是紅黑樹或者連結串列. 當 是紅黑樹的時候
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value); // 當為紅黑樹時,呼叫putTreeVal方法,此方法內部會遍歷紅黑樹,如果已存在則返回被覆蓋的Node,否則返回null
else { // 當為連結串列時
for (int binCount = 0;; ++binCount) { // 迴圈遍歷連結串列
if ((e = p.next) == null) { // 當p.next為空時
p.next = newNode(hash, key, value, null);// 則newNode方法新建一個Node物件賦值給p.next
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st // 當連結串列達到一定長度(預設8)後,呼叫treeifyBin方法轉換成紅黑樹的結構
treeifyBin(tab, hash); // 當遍歷到p.next為空時 則表示找到了可以放置put進來的key-value的地方,所以要結束連結串列遍歷
}
if (e.hash == hash&& ((k = e.key) == key || (key != null && key.equals(k)))) // 當p.next不為空時,並且hash值和傳入的hash值相等,key值也相等時,則覆蓋值,結束迴圈
break; // 則表示覆蓋值,結束迴圈
p = e; //因為連結串列的遍歷是 p.next.next.next...這種形式,所以最後需要將e賦給p
}
}
if (e != null) { // existing mapping for key //此處是真正執行覆蓋操作的地方
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) //onlyIfAbsent傳入為false,所以會直接覆蓋e原來的value值
e.value = value;
afterNodeAccess(e); //afterNodeAccess此方法下面沒有程式碼所以無實際意義
return oldValue; // 這個地方可以看出 當我們put重複的key-value時 會返回被覆蓋的oldValue,雖然我們一般不關心put時候的返回值
}
}
++modCount; //modCount是統計map被修改次數的
if (++size > threshold) //當tab的size大於閾值的時候則需要呼叫resize方法進行擴容
resize();afterNodeInsertion(evict); //afterNodeInsertion此方法下面沒有程式碼所以無實際意義
return null; //當put的時候不存在覆蓋值的時候則返回null
}