HashMap之元素刪除
微信公眾號:如有問題或建議,請在下方留言;
最近更新:2018-09-18
HashMap之元素刪除
繼上一篇HashMap之元素插入,我們繼續來看下元素刪除的實現原理。
1、原始碼:
1public V remove(Object key) { 2Node<K,V> e; 3return (e = removeNode(hash(key), key, null, false, true)) == null ? 4null : e.value; 5} 複製程式碼
看下核心方法removeNode:
1//matchValue為false 表示不需要比對value值一致 2//movable為false 表示刪除節點後不移動其他節點 3final Node<K,V> removeNode(int hash, Object key, Object value, 4boolean matchValue, boolean movable) { 5//p為當前檢查的節點 6Node<K,V>[] tab; Node<K,V> p; int n, index; 7if ((tab = table) != null && (n = tab.length) > 0 && 8(p = tab[index = (n - 1) & hash]) != null) { //待刪除節點在陣列索引位置存在元素 9//node為找到的刪除節點 10Node<K,V> node = null, e; K k; V v; 11if (p.hash == hash && 12((k = p.key) == key || (key != null && key.equals(k))))//雜湊值一致,key一致則找到了要刪除的節點 13node = p; 14else if ((e = p.next) != null) {//未找到則看後繼節點 15if (p instanceof TreeNode)//如果後繼節點為紅黑樹節點,則在紅黑樹中查詢要刪除的節點 16node = ((TreeNode<K,V>)p).getTreeNode(hash, key); 17else { 18do { 19if (e.hash == hash && 20((k = e.key) == key || 21(key != null && key.equals(k)))) { 22node = e; 23break; 24} 25p = e; 26} while ((e = e.next) != null);//不為紅黑樹節點,則遍歷單鏈表查詢 27} 28} 29if (node != null && (!matchValue || (v = node.value) == value || 30(value != null && value.equals(v)))) {//找到節點,matchValue為true,還需要比對value值 31if (node instanceof TreeNode) 32((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);//待刪除節點為紅黑樹節點,則進行紅黑樹節點的刪除操作 33else if (node == p)//待刪除節點為陣列中的元素,直接將後繼節點替換即可 34tab[index] = node.next; 35else//待刪除節點為單鏈表中的元素,將後繼節點作為前驅節點的後繼節點即可 36p.next = node.next; 37++modCount; 38--size; 39afterNodeRemoval(node); 40return node; 41} 42} 43return null; 44} 複製程式碼
2、流程圖:

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

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

3、說明:
以上為HashMap的紅黑樹刪除流程,其實思路和TreeMap中紅黑樹大致相同,關於TreeMap的解析,請看TreeMap之元素刪除,文章中我對紅黑樹的刪除過程進行了詳細的分析,這裡就不做詳細闡述了。
示例
我們通過一個具體的例子,來體會下刪除的過程,請看:





















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