HashMap源碼解析

本文件出處https://shenyifengtk.github.io/2020/05/10/HashMap源碼解析
轉(zhuǎn)載請說明出處

以后面試官問你HashMap原理平道,不能再答數(shù)組+鏈表的結(jié)構(gòu)了臊泌,JDK1.8 添加了紅黑樹結(jié)構(gòu)篷店,知識要與時俱進(jìn)啊。受限于文章篇幅瘪弓,我會從HashMap 初始化劫映,put、get方面解析底層原理府蛇。

內(nèi)部設(shè)定

常量設(shè)定

    /**
     * HashMap 默認(rèn)初始化長度
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * HashMap 允許容器數(shù)組最大長度
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 負(fù)載因子集索,容器數(shù)量/容器最大長度 大于等于,數(shù)組進(jìn)行擴容
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     *  鏈表的長度超過閾值汇跨,在添加元素的時候鏈表轉(zhuǎn)化成紅黑樹
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 在擴容的時候务荆,紅黑樹數(shù)量小于閾值轉(zhuǎn)換成鏈表
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 最小table 容器數(shù)量支持鏈表樹化
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

內(nèi)部元素結(jié)構(gòu)

 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

Node對象只擁有三個屬性,key穷遂,value函匕,next,結(jié)構(gòu)非常簡單蚪黑,操作也簡單盅惜。

內(nèi)部屬性變量

   /**
     * 內(nèi)部數(shù)組,上面所說table 指的就是這個
     */
    transient Node<K,V>[] table;

    /**
     * entrySet() 返回容器
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * 鍵值對的數(shù)量忌穿,也是HashMap size
     */
    transient int size;

    /**
     * HashMap 結(jié)構(gòu)修改次數(shù)
     */
    transient int modCount;

    /**
     * 擴容table 閾值
     */
    int threshold;

    /**
     *  擴容因子
     */
    final float loadFactor;
  

解析代碼

構(gòu)造函數(shù)

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

這段代碼很簡單抒寂,主要是判斷兩個輸入?yún)?shù)是否合法,將容器長度重新計算擴容閾值掠剑。
看下tableSizeFor怎么計算閾值

/**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

這個方法其實通過輸入數(shù)屈芜,返回一個最接近大于等于他的2次方數(shù)。
通過將cap 高位1不斷右移,返回cap最近2次方的數(shù)井佑。如果有同學(xué)看不懂這個算法属铁,可以參考這位老哥寫的https://blog.csdn.net/fan2012huan/article/details/51097331,解析得非常易懂躬翁。
上面的構(gòu)造函數(shù)焦蘑,并沒有初始化Node 數(shù)組,我進(jìn)入put方法看下怎么樣初始化的姆另!

添加元素

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @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) {
       //p 如果存在就是鏈頭
        Node<K,V>[] tab; Node<K,V> p; int n, i;
         //數(shù)組未初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
         //根據(jù)hash 值 計算數(shù)組下標(biāo)位置喇肋,這個位置下沒有元素
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null); //插入元素
        else {//hash 值沖突
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))) //key 相等
                e = p;
            else if (p instanceof TreeNode) //紅黑樹
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else { 
                for (int binCount = 0; ; ++binCount) { //遍歷鏈表,插入數(shù)據(jù)
                    if ((e = p.next) == null) { //下一個元素為空迹辐,說明已經(jīng)遍歷到最后一個元素 蝶防,插入 終止循環(huán)
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // 到達(dá)轉(zhuǎn)化成樹鏈的閾值
                            treeifyBin(tab, hash); //將鏈表轉(zhuǎn)換成樹
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) //出現(xiàn)key 相等
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e); //空方法 ,LinkedHashMap 實現(xiàn)
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold) //超出閾值 
            resize();
        afterNodeInsertion(evict);
        return null;
    }

整體流程: 先判斷數(shù)組為空明吩,沒有則使用resize()方法初始化數(shù)組间学,使用hash 值& 數(shù)組長度計算下標(biāo),下標(biāo)值為空印荔,直接插入低葫。處理hash沖突情況,先判斷鏈頭key是否相等仍律,判斷數(shù)組Node是否樹鏈嘿悬,則使用putTreeVal方法插入樹鏈中,否則遍歷鏈表在結(jié)尾插入水泉。最后判斷數(shù)組元素是否超過閾值善涨,使用reszie()擴容。這個流程基本和我們平常寫業(yè)務(wù)代碼差不多草则。

計算hash方法

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

假設(shè)沒有這個hash算法钢拧,對象hashCode直接&數(shù)組長度(n-1),當(dāng)n比較小的時候,hash雖然是32位整數(shù)炕横,但是只要低位是有效的源内,hash沖突概率比較大,會導(dǎo)致鏈表比較長份殿。使用hash方法將高16位影響到低16位膜钓,將低16位的二進(jìn)制打散了,減少了hash沖突概率卿嘲。
resize擴容颂斜,初始化數(shù)組方法

    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) { //大于最大數(shù)組長度,不會進(jìn)行擴容操作
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // 使用構(gòu)造函數(shù)cap初始化數(shù)組長度
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            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"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //創(chuàng)建新數(shù)組
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null; //刪除數(shù)組元素
                    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 { //將鏈條分成兩個鏈條焚鲜,一個是原數(shù)組下標(biāo)掌唾,另一個則下標(biāo) x 2移動下標(biāo)
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            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); //將兩種鏈條的鏈頭元素插入到數(shù)組中
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

整體流程還是比較簡單的放前,先確定數(shù)組長度忿磅,threshold屬性,創(chuàng)建新數(shù)組移動copy元素凭语,刪除原數(shù)組元素葱她。這個方法基本就是初始化table數(shù)組,擴容數(shù)組似扔,處理鏈條或樹鏈的內(nèi)部細(xì)節(jié)吨些。
if ((e.hash & oldCap) == 0) { 這個寫法,看了好久才明白什么意思啊炒辉。我們都知道數(shù)組長度永遠(yuǎn)都是2的次方豪墅,二進(jìn)制表現(xiàn)就是最高位是1 其他都是0, 比如默認(rèn)長度16 黔寇,二進(jìn)制就是10000偶器。 計算數(shù)組長度算法e.hash & (oldCap- 1), 長度-1 二機制的數(shù)11111,先數(shù)組擴容長度 * 2缝裤,計算下標(biāo)結(jié)果下標(biāo)前面不變屏轰,最高位是0還是1,是0保存下標(biāo)不變憋飞,是1表示下標(biāo)需要x 2.霎苗。覺得講得不清楚,可以看下圖

image

下面了解拆分樹鏈的split

/**     
         * @param map the map
         * @param tab the table for recording bin heads
         * @param index the index of the table being split
         * @param bit the bit of hash to split on
         */
        final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
            TreeNode<K,V> b = this;
            // 按hash 計算結(jié)果區(qū)分兩個樹鏈榛做,統(tǒng)計樹鏈的長度
            TreeNode<K,V> loHead = null, loTail = null;
            TreeNode<K,V> hiHead = null, hiTail = null;
            int lc = 0, hc = 0;
            for (TreeNode<K,V> e = b, next; e != null; e = next) {
                next = (TreeNode<K,V>)e.next;
                e.next = null;
                if ((e.hash & bit) == 0) {
                    if ((e.prev = loTail) == null)
                        loHead = e;
                    else
                        loTail.next = e;
                    loTail = e;
                    ++lc;
                }
                else {
                    if ((e.prev = hiTail) == null)
                        hiHead = e;
                    else
                        hiTail.next = e;
                    hiTail = e;
                    ++hc;
                }
            }

            if (loHead != null) {
                if (lc <= UNTREEIFY_THRESHOLD)
                    tab[index] = loHead.untreeify(map); //拆分樹鏈轉(zhuǎn)化成鏈條結(jié)構(gòu)
                else {
                    tab[index] = loHead;
                    if (hiHead != null) // (else is already treeified)
                        loHead.treeify(tab);
                }
            }
            if (hiHead != null) {
                if (hc <= UNTREEIFY_THRESHOLD)
                    tab[index + bit] = hiHead.untreeify(map);
                else {
                    tab[index + bit] = hiHead;
                    if (loHead != null)
                        hiHead.treeify(tab);
                }
            }
        }

首先要知道TreeNode間接繼承Node唁盏,擁有單向鏈條的屬性。理解上面流程就很簡單了瘤睹,先遍歷樹鏈升敲,按照hash & 數(shù)組長度方式,分成移動下標(biāo)和不移動兩個類型樹鏈轰传。再判斷鏈條數(shù)量是否大于UNTREEIFY_THRESHOLD(6) ,滿足使用untreeify方法拆分成鏈條驴党。如果樹鏈頭不為空,則說明紅黑樹結(jié)構(gòu)依然存在获茬,使用treeify方法將鏈表轉(zhuǎn)換成樹鏈港庄。
要理解鏈表怎么轉(zhuǎn)化成樹鏈,必須深入treeify方法

        final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            //從this 鏈頭開始遍歷
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                if (root == null) { //root 根元素
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
                    for (TreeNode<K,V> p = root;;) {
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);

                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) { //如果子節(jié)點黑紅都是null恕曲,直接插入指向鹏氧,否則向下遞歸
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                            root = balanceInsertion(root, x); //調(diào)整節(jié)點位置,著色佩谣,左旋或者右旋
                            break;
                        }
                    }
                }
            }
            moveRootToFront(tab, root); //判斷root是否根節(jié)點把还,如果不是插入根節(jié)點位置
        }

看到這里,大家是不是跟我一樣,覺得代碼寫得還是比較簡單的吊履,沒什么難度啊安皱。下面代碼就不是這樣了,沒有一點黑紅樹基礎(chǔ)的同學(xué)艇炎,麻煩先補下課先酌伊,不然你很難理解代碼什么意思。

紅黑樹的性質(zhì)
  1. 節(jié)點是紅色或黑色缀踪。
  2. 根節(jié)點是黑色居砖。
  3. 所有葉子都是黑色(葉子是NIL節(jié)點)
  4. 每個紅色節(jié)點的兩個孩子節(jié)點必須是黑色. (從葉子到根的所有路徑上不能有兩個連續(xù)的紅色節(jié)點)
  5. 從任意節(jié)點到其葉子節(jié)點的所有路徑都包含相同數(shù)目的黑色節(jié)點。

紅黑樹.png

在紅黑樹插入或刪除情況驴娃,上面的特征可能會被打破奏候。所以要使用balanceInsertion方法平衡樹鏈結(jié)構(gòu)。我們在分析代碼的時候唇敞,一定要把上面5個性質(zhì)帶上有助于理解鼻由。

static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
            x.red = true; //默認(rèn)x 就是紅色 
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                //x 的父節(jié)點xp為空,則說明x 就是根節(jié)點 , 根據(jù)1屬性厚棵,節(jié)點變成黑色就可以了
                if ((xp = x.parent) == null) {
                    x.red = false;
                    return x;
                }
                //父節(jié)點黑色蕉世,插入紅色,以上條件都滿足婆硬,終止循環(huán)
                else if (!xp.red || (xpp = xp.parent) == null)
                    return root;
               
                if (xp == (xppl = xpp.left)) {
                    //見圖1-1 狠轻,不滿足性質(zhì)4、性質(zhì)5彬犯,改變著色向楼,向上遞歸
                    if ((xppr = xpp.right) != null && xppr.red) {
                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        //見圖1-2 不滿足 性質(zhì)4,xp左移 
                        if (x == xp.right) {
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        // 圖1-3情況 不滿足性質(zhì)4谐区,xpp 右移 調(diào)整著色
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                else { //位置相反湖蜕,流程一樣,位置互換即可
                    if (xppl != null && xppl.red) {
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        if (x == xp.left) {
                            root = rotateRight(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }

上面的代碼配合這三個圖片宋列,看出1-3 處理完后昭抒,剛好是圖1-1 處理條件,向上遞歸炼杖,直到終止循環(huán)灭返。這個就是插入后,調(diào)整紅黑樹平衡整體流程坤邪。這些代碼熙含,我當(dāng)時看了一整天也明白什么意思,后面結(jié)合紅黑樹知識艇纺,自己畫圖對應(yīng)代碼流程怎静,理解起來就很簡單了邮弹。

圖1-1

圖1-2

圖1-3

有了這些理解在去看putTreeVal方法就很簡單了

        /**
         * Tree version of putVal.
         */
        final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                       int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            TreeNode<K,V> root = (parent != null) ? root() : this;
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                    return p;
               // 獲取key 類型,判斷是是compare接口 比較大小
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    if (!searched) {
                        TreeNode<K,V> q, ch;
                        searched = true;
                        if (((ch = p.left) != null &&
                             (q = ch.find(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.find(h, k, kc)) != null))
                            return q;
                    }
                    dir = tieBreakOrder(k, pk); //使用類名蚓聘,原生hash比較大小
                }

                TreeNode<K,V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    Node<K,V> xpn = xp.next;
                    TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    xp.next = x;
                    x.parent = x.prev = xp;
                    if (xpn != null)
                        ((TreeNode<K,V>)xpn).prev = x;
                    moveRootToFront(tab, balanceInsertion(root, x));
                    return null;
                }
            }
        }

遍歷樹鏈肠鲫,比較hash 大小,找到子節(jié)點插入位置或粮,使用balanceInsertion方法平衡紅黑樹。

獲取value get()方法分析

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 && //空數(shù)組不指望了
            (first = tab[(n - 1) & hash]) != null) { //計算數(shù)組下標(biāo)位置
            if (first.hash == hash && // 獲取到value 
                ((k = first.key) == key || (key != null && key.equals(k))))
                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;
    }

看下紅黑樹查找

        final TreeNode<K,V> getTreeNode(int h, Object k) {
            return ((parent != null) ? root() : this).find(h, k, null);
        }

        final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }

        final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
            TreeNode<K,V> p = this;
            do {
                int ph, dir; K pk;
                TreeNode<K,V> pl = p.left, pr = p.right, q;
                if ((ph = p.hash) > h) //大于往左邊
                    p = pl;
                else if (ph < h) //小于往右邊
                    p = pr;
                else if ((pk = p.key) == k || (k != null && k.equals(pk))) //找到了
                    return p;
                else if (pl == null) //遍歷到最底下一個節(jié)點為止氯材,這個p節(jié)點肯定沒有字節(jié)點
                    p = pr;
                else if (pr == null)
                    p = pl;
                else if ((kc != null ||
                          (kc = comparableClassFor(k)) != null) &&
                         (dir = compareComparables(kc, k, pk)) != 0)
                    p = (dir < 0) ? pl : pr;
                else if ((q = pr.find(h, k, kc)) != null)
                    return q;
                else
                    p = pl;
            } while (p != null);
            return null;
        }

刪除節(jié)點

    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))  //根據(jù)key 找到value
                node = p;
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key); //使用紅黑樹尋找
                else {
                    do { //鏈表遍歷查找
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) { 
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null); 
                }
            }
            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); //在紅黑樹中刪除,這個有點復(fù)雜
                else if (node == p) //如果p剛好是鏈頭硝岗,指引刪除
                    tab[index] = node.next;
                else //p 剛好表示前一位氢哮,node 要被刪除,p指向node 下一個節(jié)點型檀,即可刪除冗尤。
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

數(shù)組和鏈表刪除還是比較簡單的,下標(biāo)指引變更即可胀溺。紅黑樹元素刪除就相當(dāng)復(fù)雜了裂七,比插入數(shù)據(jù)要麻煩等多了

        final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                                  boolean movable) {
            int n;
            if (tab == null || (n = tab.length) == 0)
                return;
            int index = (n - 1) & hash;
            TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
            TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
            if (pred == null) //前置節(jié)點為空,將鏈頭指向succ仓坞,即子節(jié)點
                tab[index] = first = succ;
            else  //將前置節(jié)點指向next背零,相當(dāng)于刪除this
                pred.next = succ;
            if (succ != null) //刪除
                succ.prev = pred;
            if (first == null)
                return;
            if (root.parent != null)
                root = root.root();
            if (root == null //根據(jù)紅黑樹特性,最短路徑不能小于最長路徑1/2无埃,樹鏈長度不足 2 +4 = 6徙瓶,拆分
                || (movable
                    && (root.right == null
                        || (rl = root.left) == null
                        || rl.left == null))) {
                tab[index] = first.untreeify(map);  // too small
                return;
            }
            TreeNode<K,V> p = this, pl = left, pr = right, replacement;
            if (pl != null && pr != null) {
                TreeNode<K,V> s = pr, sl;
                while ((sl = s.left) != null) // 找到右孩子的左子樹找最小值
                    s = sl;
                boolean c = s.red; s.red = p.red; p.red = c; // 交換顏色
                TreeNode<K,V> sr = s.right;
                TreeNode<K,V> pp = p.parent;
                if (s == pr) { // s 是  p直系右子節(jié)點 ,交換位置
                    p.parent = s;
                    s.right = p;
                }
                else {
                    TreeNode<K,V> sp = s.parent;
                    if ((p.parent = sp) != null) { //p 移動到s 位置
                        if (s == sp.left)
                            sp.left = p;
                        else
                            sp.right = p;
                    }
                    if ((s.right = pr) != null) //s 移動到位置
                        pr.parent = s;
                }
                p.left = null;
                if ((p.right = sr) != null)   //p 右節(jié)點 和s 關(guān)聯(lián)
                    sr.parent = p;
                if ((s.left = pl) != null)  //s 和p子左邊節(jié)點pl關(guān)聯(lián)起來
                    pl.parent = s;
                if ((s.parent = pp) == null) // s 父節(jié)點 指向 p 父節(jié)點  s 已經(jīng)完全替換p 
                    root = s;
                else if (p == pp.left) 
                    pp.left = s;
                else
                    pp.right = s;
                if (sr != null)
                    replacement = sr;
                else
                    replacement = p;
            }
            else if (pl != null) //只有左節(jié)點情況
                replacement = pl;
            else if (pr != null) //只有右節(jié)點情況
                replacement = pr;
            else
                replacement = p;
            if (replacement != p) {
                TreeNode<K,V> pp = replacement.parent = p.parent;
                if (pp == null)
                    (root = replacement).red = false;
                else if (p == pp.left)
                    pp.left = replacement;
                else
                    pp.right = replacement;
                p.left = p.right = p.parent = null;
            }
            //調(diào)整元素在紅黑樹中著色嫉称,保持平衡
            TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

            if (replacement == p) {  // 刪除元素p
                TreeNode<K,V> pp = p.parent;
                p.parent = null;
                if (pp != null) {
                    if (p == pp.left)
                        pp.left = null;
                    else if (p == pp.right)
                        pp.right = null;
                }
            }
            if (movable)
                moveRootToFront(tab, r);
        }

簡單說一下侦镇,這個方法代碼是思路,首先處理最簡單的鏈表刪除元素织阅,將鏈表中對象的引用改成旁邊的元素壳繁。
紅黑樹刪除元素則比較復(fù)雜點,考慮的情況比較多荔棉。比如刪除節(jié)點在底下氮趋,沒有子節(jié)點,還要考慮元素的著手江耀,如果是紅色則可以直接刪除剩胁,黑色需要著色。如果擁有一個左節(jié)點祥国、右節(jié)點或者兩個都要昵观,情況就更加復(fù)雜了晾腔。這個方法大部分代碼都是在處理左右節(jié)點不為空的情況,將刪除節(jié)點位置和右節(jié)點下最左邊的子節(jié)點交換位置啊犬,并且交換著色灼擂。為什么要這么做呢?主要是因為這個節(jié)點的hash值和刪除節(jié)點值最為接近觉至,紅黑樹的特性小于在左邊剔应,大于在右邊,大于刪除值的最小值语御,就整個紅黑樹最接近刪除的值峻贮。在調(diào)整紅黑樹著色,任務(wù)就算完成了应闯。

心得

整體來說HashMap的代碼并不是很復(fù)雜纤控,底層業(yè)務(wù)代碼,其實就跟我們平常寫的crud代碼碉纺。但是我在看代碼的時候船万,有一些地方好難看懂啊,主要是涉及到紅黑樹的知識點骨田。很多程序員(包括我)平常都會抱怨數(shù)據(jù)結(jié)構(gòu)沒什么用耿导,在工作中用不上,其實我們掌握不夠熟練态贤,在工作上不知道使用碎节。

參考文獻(xiàn)

紅黑樹特性
hash算法解析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市抵卫,隨后出現(xiàn)的幾起案子狮荔,更是在濱河造成了極大的恐慌,老刑警劉巖介粘,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殖氏,死亡現(xiàn)場離奇詭異,居然都是意外死亡姻采,警方通過查閱死者的電腦和手機雅采,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慨亲,“玉大人婚瓜,你說我怎么就攤上這事⌒炭茫” “怎么了巴刻?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蛉签。 經(jīng)常有香客問我胡陪,道長沥寥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任柠座,我火速辦了婚禮邑雅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妈经。我一直安慰自己淮野,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布吹泡。 她就那樣靜靜地躺著骤星,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荞胡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天了嚎,我揣著相機與錄音泪漂,去河邊找鬼。 笑死歪泳,一個胖子當(dāng)著我的面吹牛萝勤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呐伞,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼敌卓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伶氢?” 一聲冷哼從身側(cè)響起趟径,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎癣防,沒想到半個月后蜗巧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡蕾盯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年幕屹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片级遭。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡望拖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挫鸽,到底是詐尸還是另有隱情说敏,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布丢郊,位于F島的核電站像云,受9級特大地震影響锌雀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜迅诬,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一腋逆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侈贷,春花似錦惩歉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搏屑,卻和暖如春争涌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辣恋。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工亮垫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伟骨。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓饮潦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親携狭。 傳聞我的和親對象是個殘疾皇子继蜡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,047評論 2 355