HashMap 之元素刪除

SevenBlue發表於2018-09-18

微信公眾號:I am CR7
如有問題或建議,請在下方留言;
最近更新:2018-09-18

HashMap之元素刪除

     繼上一篇HashMap之元素插入,我們繼續來看下元素刪除的實現原理。

1、原始碼:
1public V remove(Object key) {
2    Node<K,V> e;
3    return (e = removeNode(hash(key), key, nullfalsetrue)) == null ?
4        null : e.value;
5}
複製程式碼

看下核心方法removeNode:

 1//matchValue為false 表示不需要比對value值一致
2//movable為false 表示刪除節點後不移動其他節點
3final Node<K,V> removeNode(int hash, Object key, Object value,
4                           boolean matchValue, boolean movable)
 
{
5    //p為當前檢查的節點
6    Node<K,V>[] tab; Node<K,V> p; int n, index;
7    if ((tab = table) != null && (n = tab.length) > 0 &&
8        (p = tab[index = (n - 1) & hash]) != null) { //待刪除節點在陣列索引位置存在元素
9        //node為找到的刪除節點
10        Node<K,V> node = null, e; K k; V v;
11        if (p.hash == hash &&
12            ((k = p.key) == key || (key != null && key.equals(k))))//雜湊值一致,key一致則找到了要刪除的節點
13            node = p;
14        else if ((e = p.next) != null) {//未找到則看後繼節點
15            if (p instanceof TreeNode)//如果後繼節點為紅黑樹節點,則在紅黑樹中查詢要刪除的節點
16                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
17            else {
18                do {
19                    if (e.hash == hash &&
20                        ((k = e.key) == key ||
21                         (key != null && key.equals(k)))) {
22                        node = e;
23                        break;
24                    }
25                    p = e;
26                } while ((e = e.next) != null);//不為紅黑樹節點,則遍歷單連結串列查詢
27            }
28        }
29        if (node != null && (!matchValue || (v = node.value) == value ||
30                             (value != null && value.equals(v)))) {//找到節點,matchValue為true,還需要比對value值
31            if (node instanceof TreeNode)
32                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);//待刪除節點為紅黑樹節點,則進行紅黑樹節點的刪除操作
33            else if (node == p)//待刪除節點為陣列中的元素,直接將後繼節點替換即可
34                tab[index] = node.next;
35            else//待刪除節點為單連結串列中的元素,將後繼節點作為前驅節點的後繼節點即可
36                p.next = node.next;
37            ++modCount;
38            --size;
39            afterNodeRemoval(node);
40            return node;
41        }
42    }
43    return null;
44}
複製程式碼

2、流程圖:

圖注:刪除元素流程圖
刪除元素流程圖

3、說明:

     因為HashMap存在三種儲存方式,陣列、單連結串列、紅黑樹,那麼刪除元素時必然存在著這三種情況。其中,紅黑樹的刪除最為複雜,我們們接著往下看。

紅黑樹之查詢元素

1、原始碼:

 1final TreeNode<K,V> getTreeNode(int h, Object k) {
2    return ((parent != null) ? root() : this).find(h, k, null);
3}
4
5//查詢紅黑樹的根節點
6final TreeNode<K,V> root() {
7    for (TreeNode<K,V> r = this, p;;) {
8        if ((p = r.parent) == null)
9            return r;
10        r = p;
11    }
12}
13
14//遍歷紅黑樹查詢指定雜湊和key的節點
15final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
16    TreeNode<K,V> p = this;
17    do {
18        int ph, dir; K pk;
19        TreeNode<K,V> pl = p.left, pr = p.right, q;//儲存左節點 右節點 
20        if ((ph = p.hash) > h) //左節點雜湊值大於給定查詢節點的雜湊值,則繼續往左找
21            p = pl;
22        else if (ph < h)//左節點雜湊值小於給定查詢節點的雜湊值,則往右找
23            p = pr;
24        else if ((pk = p.key) == k || (k != null && k.equals(pk)))//當前節點key值一致,則返回該節點
25            return p;
26        else if (pl == null)//左節點為空,則往右找
27            p = pr;
28        else if (pr == null)//右節點為空,則往左找
29            p = pl;
30        else if ((kc != null ||//雜湊相同,key不同,且有左右節點。此時看key是否可比較,是則比較key值
31                  (kc = comparableClassFor(k)) != null) &&
32                 (dir = compareComparables(kc, k, pk)) != 0)
33            p = (dir < 0) ? pl : pr;//小於往左,大於往右
34        else if ((q = pr.find(h, k, kc)) != null)//雜湊相同,key不可比或者key也相同,則往右查詢
35            return q;
36        else//否則往左
37            p = pl;
38    } while (p != null);
39    return null;
40}
複製程式碼

2、流程圖:

圖注:查詢元素流程圖
查詢元素流程圖

紅黑樹之刪除元素

1、原始碼:

  1final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
2                          boolean movable)
 
{
3    int n;
4    if (tab == null || (n = tab.length) == 0)
5        return;
6    int index = (n - 1) & hash;
7    TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
8    TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
9    if (pred == null//待刪除節點為根節點,則其後繼節點作為陣列索引位置的元素
10        tab[index] = first = succ;
11    else//待刪除節點存在前驅節點,則後繼節點作為前驅節點的下一個節點
12        pred.next = succ;
13    if (succ != null)//待刪除節點存在後繼節點,則前驅節點作為後繼節點的上一個節點
14        succ.prev = pred;
15    if (first == null)//陣列索引位置元素為null,直接返回
16        return;
17    if (root.parent != null)//找到紅黑樹的根節點
18        root = root.root();
19    if (root == null || root.right == null ||
20        (rl = root.left) == null || rl.left == null) {//紅黑樹太小則進行去樹化操作
21        tab[index] = first.untreeify(map);  // too small
22        return;
23    }
24    //查詢替代節點replacement
25    //p為待刪除節點,pl為其左節點,pr為其右節點,replacement為替代節點
26    TreeNode<K,V> p = this, pl = left, pr = right, replacement;
27    if (pl != null && pr != null) {//待刪除節點有左右節點
28       //s為後繼節點 
29TreeNode<K,V> s = pr, sl;
30        while ((sl = s.left) != null)//往待刪除節點右子樹的左邊走 
31            s = sl;
32        boolean c = s.red; s.red = p.red; p.red = c;//互換後繼節點和待刪除節點的顏色
33        //sr為後繼節點的右節點
34        TreeNode<K,V> sr = s.right;
35        //pp為待刪除節點的父節點
36        TreeNode<K,V> pp = p.parent;
37        if (s == pr) {//待刪除節點的右節點無左孩子--->右節點和待刪除節點互換
38            p.parent = s;
39            s.right = p;
40        }
41        else {//待刪除節點的右節點有左孩子
42            //sp為後繼節點的父節點
43            TreeNode<K,V> sp = s.parent;
44            //後繼節點存在父節點,則讓待刪除節點替代後繼節點
45            if ((p.parent = sp) != null) {//後繼節點的父節點成為待刪除節點的父節點
46                if (s == sp.left)//後繼節點為其父節點的左孩子
47                    sp.left = p;//待刪除節點就作為後繼節點的父節點的左孩子
48                else
49                    sp.right = p;//待刪除節點就作為後繼節點的父節點的右孩子
50            }
51            //待刪除節點存在右節點,則讓後繼節點成為其父節點
52            if ((s.right = pr) != null)
53                pr.parent = s;
54        }
55        p.left = null;//待刪除節點左孩子為null
56        if ((p.right = sr) != null)//後繼節點存在右節點,則讓其成為待刪除節點的右節點
57            sr.parent = p;//相對應,待刪除節點成為其父節點
58        if ((s.left = pl) != null)//待刪除節點存在左節點,則讓其成為後繼節點的左節點
59            pl.parent = s;//相對應,後繼節點成為其父節點
60        //待刪除節點存在父節點,則讓後繼節點替代待刪除節點
61        if ((s.parent = pp) == null)//待刪除節點不存在父節點,則後繼節點父節點為null
62            root = s;//後繼節點成為根節點
63        else if (p == pp.left)//待刪除節點存在父節點,且待刪除節點是其左節點
64            pp.left = s;//後繼節點作為其左節點
65        else
66            pp.right = s;//後繼節點作為其右節點
67        //後繼節點存在右節點,則替代節點為該節點
68        if (sr != null)
69            replacement = sr;
70        else //替代節點為待刪除節點(等於未找到)
71            replacement = p;
72    }
73    else if (pl != null)//待刪除節點只有左節點
74        replacement = pl;
75    else if (pr != null)//待刪除節點只有右節點
76        replacement = pr;
77    else//待刪除節點為葉子節點
78        replacement = p;
79    if (replacement != p) {//替代節點不為待刪除節點,則先進行節點刪除,然後進行平衡調整
80        TreeNode<K,V> pp = replacement.parent = p.parent;
81        if (pp == null)
82            root = replacement;
83        else if (p == pp.left)
84            pp.left = replacement;
85        else
86            pp.right = replacement;
87        p.left = p.right = p.parent = null;
88    }
89
90    TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);//進行平衡調整
91
92    if (replacement == p) {  //替代節點為待刪除節點,則先進行平衡調整,然後進行節點刪除
93        TreeNode<K,V> pp = p.parent;
94        p.parent = null;
95        if (pp != null) {
96            if (p == pp.left)
97                pp.left = null;
98            else if (p == pp.right)
99                pp.right = null;
100        }
101    }
102    if (movable)//將紅黑樹根節點移動到陣列索引位置
103        moveRootToFront(tab, r);
104}
複製程式碼

2、流程圖:

圖注:刪除元素流程圖
刪除元素流程圖

3、說明:

     以上為HashMap的紅黑樹刪除流程,其實思路和TreeMap中紅黑樹大致相同,關於TreeMap的解析,請看TreeMap之元素刪除,文章中我對紅黑樹的刪除過程進行了詳細的分析,這裡就不做詳細闡述了。

示例

     我們通過一個具體的例子,來體會下刪除的過程,請看:

圖注:初始狀態
初始狀態

圖注:刪除10
刪除10

圖注:刪除226
刪除226

圖注:刪除320
刪除320

圖注:替代刪除
替代刪除

圖注:平衡調整
平衡調整

圖注:刪除384
刪除384

圖注:平衡調整
平衡調整

圖注:刪除
刪除

圖注:刪除0
刪除0

圖注:平衡調整
平衡調整

圖注:平衡調整
平衡調整

圖注:刪除
刪除

圖注:刪除64
刪除64

圖注:刪除
刪除

圖注:刪除128
刪除128

圖注:平衡調整
平衡調整

圖注:平衡調整
圖注:平衡調整

圖注:刪除
刪除

圖注:刪除512
刪除512

圖注:去樹化
去樹化

總結

     通過上述的分析,結合著TreeMap之元素刪除這篇文章,我想,要理解HashMap的刪除,並不是一件難事。
     文章的最後,感謝大家的支援,歡迎掃描下方二維碼,進行關注。如有任何疑問,歡迎大家留言。

HashMap 之元素刪除

相關文章