HashMap源碼分析(一)

這次只講解部分源碼,不會全部講完。并且代碼來自API 26 Platfrom。前段時間又重新簡單看了一次HashMap的源碼,想簡單記錄一下碰到的問題和在源碼中能參考到的代碼寫法。

我先提出我的幾個問題,如果有大佬路過的話麻煩請幫忙解答一下:
為什么獲取hash的時候要與自身的右移動16位做異或運算
為什么根據(jù)key生成下標(biāo)的做法是 (n - 1) & hash
為什么擴展數(shù)組的時候拆分鏈表的條件是e.hash & oldCap

接下來進入正題

1.HashMap節(jié)點結(jié)構(gòu)體

可以先看看節(jié)點的數(shù)據(jù)結(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;
        }
    }

用一個靜態(tài)內(nèi)部類來定義節(jié)點,節(jié)點里面有4個屬性
(1)hash:這個節(jié)點的hashcode
(2)key/value:鍵值對
(3)next:指向下一個節(jié)點的指針
hashmap內(nèi)部的操作基本都是對節(jié)點進行操作。

2.重要參數(shù)

hashmap中有幾個重要的參數(shù),在源碼中也有明顯的注釋



這樣的注釋可以讓開發(fā)者更快的找到相應(yīng)的功能模塊,如果一個類里面代碼量多的話我也會這么寫注釋。

transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
transient int size;
transient int modCount;
int threshold;
final float loadFactor;

(1)table就是節(jié)點的數(shù)組,java中hashmap基本的形態(tài)就是一個鏈表數(shù)組(這是我的理解,不知道這樣說對不對,反正就是數(shù)組和鏈表的結(jié)合),table就是這個數(shù)組。
entrySet本章先不解釋
(2)size是長度,不是數(shù)組的長度,而是hashmap中包含的鍵值對的長度。
(3)modCount是操作次數(shù),這個本章也不會細講。
(4)threshold是數(shù)組的擴展容量(我的理解),往下看就知道有什么用了。
(5)loadFactor加載因子,往下看就知道有什么用了。

3.構(gòu)造方法

構(gòu)造方法是一個類的入口,所以我們先從構(gòu)造方法看。

    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);
    }

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

這里有3個構(gòu)造方法,兩個參數(shù)分別是initialCapacity表示數(shù)組容量,loadFactor表示加載因子,簡單了解下就行,按照最常見的用法,我們一般都是調(diào)用無參的構(gòu)造方法
加載因子默認(rèn)為DEFAULT_LOAD_FACTOR,也就是0.75(看下面的源碼就知道loadFactor有什么用了)

4.put方法

我們一般調(diào)用無參構(gòu)造函數(shù)實例化對象之后,就會調(diào)用put方法,也就是只設(shè)置了loadFactor的值之后就調(diào)put方法

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

第一個參數(shù)為key的hashcode,我們可以看看hashcode怎么生成的

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

看到有調(diào)用object的hashCode()方法。

    public int hashCode() {
        return identityHashCode(this);
    }

    // Android-changed: add a local helper for identityHashCode.
    // Package-private to be used by j.l.System. We do the implementation here
    // to avoid Object.hashCode doing a clinit check on j.l.System, and also
    // to avoid leaking shadow$_monitor_ outside of this class.
    /* package-private */ static int identityHashCode(Object obj) {
        int lockWord = obj.shadow$_monitor_;
        final int lockWordStateMask = 0xC0000000;  // Top 2 bits.
        final int lockWordStateHash = 0x80000000;  // Top 2 bits are value 2 (kStateHash).
        final int lockWordHashMask = 0x0FFFFFFF;  // Low 28 bits.
        if ((lockWord & lockWordStateMask) == lockWordStateHash) {
            return lockWord & lockWordHashMask;
        }
        return identityHashCodeNative(obj);
    }

    @FastNative
    private static native int identityHashCodeNative(Object obj);

簡單可以看出,這里是調(diào)用原生的方法生成的,那么我這里并沒有追去看原生怎么生成的,我只是去網(wǎng)上收看到有人說生成的hashcode是內(nèi)存地址,32位,如果不是這樣的話希望能有大佬在評論指正
那么這里獲取hash值就是用內(nèi)存地址異或內(nèi)存地址右移16位,至于為什么這樣做,我也不知道,這說明即便光看源碼,也并非什么都能看懂,我查了下,聽被人說大概的意思是這樣做能減少碰撞的次數(shù),這個我也沒認(rèn)證過。

回頭繼續(xù)看putVal方法

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            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) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

為了方便看,我把沒有走的代碼先屏蔽掉,第一次put的時候,注意,是第一次調(diào)用hashmap.put的時候

   final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
       .........
    }

我們上面調(diào)用構(gòu)造方法的時候,并沒有對table進行初始化,所以它是空,所以肯定進入這個判斷,有調(diào)一個resize()的方法,這個resize()方法很重要,主要做兩個操作,初始化table數(shù)組和擴展table數(shù)組,第一次調(diào)肯定是初始化,我同樣先屏蔽部分代碼

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) {
          ......
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            ......
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
           ......
        }
        return newTab;
    }

要注意這里定義了幾個參數(shù),
(1)oldTab:變化之前的節(jié)點數(shù)組
(2)oldCap:變化前的數(shù)組的長度
(3)oldThr :舊的threshold,也就是默認(rèn)0
(4)newCap:記錄改變后的數(shù)組長度
(5)newThr :記錄改變后的threshold

數(shù)組默認(rèn)沒初始化,所以oldCap是0,oldThr 默認(rèn)也是0,所以

            newCap = DEFAULT_INITIAL_CAPACITY;   //(1<< 4 = 16)
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // (0.75 * 16 = 12)

可以看出,第一次調(diào)用put的時候會先判斷數(shù)組有沒有初始化,如果沒有,默認(rèn)設(shè)置長度為16(1向左移動4位),threshold是數(shù)組長度的0.75倍(threshold是什么,下面會有說)
然后賦值給全局變量

        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;

好了,這里調(diào)用resize()方法就是為了初始化數(shù)組長度,還能從這個源碼中看出一種做法,就是你設(shè)計某個模塊的時候可以不用設(shè)計初始化方法,可以在調(diào)用的時候再去判斷之前有沒有初始化,沒有就先進行初始化,這樣的做法就會顯得比較靈活。

繼續(xù)看putVal方法

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            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) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

看到有4個參數(shù),tab是形參,就是節(jié)點數(shù)組,p就是記錄某個節(jié)點,n是記錄數(shù)組的長度,i是記錄某個下標(biāo)。
剛才因為第一次Table為空,所以走進這個判斷

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

調(diào)用上面resize()方法初始化數(shù)組之后,tab得到一個長度為16的數(shù)組,n等于16。
說實話,我很不喜歡賦值操作和邏輯操作寫在一起,感覺這樣寫不好看
之后往下看,做判斷

if ((p = tab[i = (n - 1) & hash]) == null)

看到&操作,肯定是位運算,n-1就是1111,用1111去和hash,一個32位的二進制做與運算,得出的結(jié)果就是下標(biāo)。簡單來說就是用某個方法生成一個0—15之前的一個小標(biāo),比如生成5,那結(jié)果就是tab[5]的值是不是空
所以第二個問題來了,生成下標(biāo)為什么要用長度-1和hash做與運算
由于我們這個第一次肯定是空的數(shù)組,所以為空

tab[i] = newNode(hash, key, value, null);

為空的話就新建一個節(jié)點賦值給這個數(shù)組的下標(biāo)。然后賦值邏輯就走完了,看看后續(xù)的邏輯

        ++modCount;  // 操作數(shù)+1
        if (++size > threshold)  
            resize();
        afterNodeInsertion(evict); // 提供給子類重寫來給子類在該步驟下做特定操作

中間的操作,鍵值對的長度加1,如果鍵值對的長度超過threshold,則對數(shù)組進行擴展
到這里應(yīng)該就能看出threshold的左右,相當(dāng)于是一個閥值,如果鍵值對的數(shù)量超過這個閥值,我們就擴展數(shù)組,這是java里面HashMap擴展長度的一個思想。

那么第一次put我們講完了,其實還挺簡單的,假如我們put第二個參數(shù)。

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            ....... // 初始化的情況上面講過了
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            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) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

根據(jù)這個Key的hash,用i = (n - 1) & hash算出下標(biāo),如果這個下標(biāo)下的值不存在,那就創(chuàng)建這個節(jié)點和放到這個下標(biāo)中,比如tab[6],我們和上面一樣,創(chuàng)建節(jié)點放入數(shù)組就結(jié)束,很簡單。但是假如算出是5,tab[5]在第一次put的時候已經(jīng)賦值了,那我們這里就要走else

        if ((p = tab[i = (n - 1) & hash]) == null)
            ......
        else {
            Node<K,V> e; K k;
            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) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }

有兩個參數(shù),e表示記錄節(jié)點,k表示Key。這命名,我得吐槽一下,咋不整個aaa呢
判斷

if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))

如果當(dāng)前的hash值和之前存的節(jié)點的hash值相同并且key也相同,那就e = p;然后

            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }

把這個節(jié)點的value換成傳進來的value,沒錯,這步就做了替換操作。
如果hash不等或者key不等,然后判斷p instanceof TreeNode,這個節(jié)點是否是紅黑樹的數(shù)據(jù)結(jié)構(gòu)。本章不討論紅黑樹的情況,你只要知道在某個節(jié)點鏈表長度到一點值的時候,這條鏈表會轉(zhuǎn)成紅黑樹就行。
假如當(dāng)前節(jié)點的數(shù)據(jù)結(jié)構(gòu)不是紅黑樹,

                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }

死循環(huán),e指向p的下一個節(jié)點(怕你亂多啰嗦一句,p就是數(shù)組中某個下標(biāo)的那個節(jié)點,也就是鏈表的頭節(jié)點)。如果e等于空(說明e處于鏈表最后一個節(jié)點的next的情況),新建一個節(jié)點加入鏈表

p.next = newNode(hash, key, value, null);

然后判斷是否需要把鏈表轉(zhuǎn)成紅黑樹

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    treeifyBin(tab, hash);

這里就印證我上面說的,當(dāng)這條鏈表長度大于等于7的時候,鏈表會轉(zhuǎn)成紅黑樹,因為我說了這篇不討論紅黑樹,所以我假設(shè)長度沒到7
如果沒到尾,e!=null,判斷e的hash和key與傳進來的相不相同,相同的話覆蓋(這里的break跳出死循環(huán)能走下面的賦值操作)

            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }

那假如key也不相等,就把指針移到下一位

p = e;

然后循環(huán)操作。

總結(jié):put方法中,先判斷節(jié)點數(shù)組初始化沒有,沒初始化的話會先初始化,然后判斷數(shù)組某個下標(biāo)是否為空,為空的話直接創(chuàng)建一個節(jié)點給這個下標(biāo),不為空的話,循環(huán)這個數(shù)組下標(biāo)下的節(jié)點的鏈表,判斷是否鏈表中的某個節(jié)點key相同,相同的話覆蓋,不相同的話創(chuàng)建一個節(jié)點添加到鏈表的最后,如果長度到達7,將鏈表轉(zhuǎn)成紅黑樹。put操作完成之后,最后判斷size的長度有沒有超過閥值,如果超過閥值,則擴展數(shù)組。

5.數(shù)組擴展resize方法

上邊有講初始化的情況,這里就直接講擴展的情況

    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) {
                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) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            ......
        }
        if (newThr == 0) {
           ......
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    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
                        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);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

oldCap在第一次擴展的情況下是16,大于0

newCap = oldCap << 1

新的數(shù)組長度就是舊的長度向左移動一位,就是32。每次擴展都是左移一位,所以擴展肯定是2的倍數(shù)

newThr = oldThr << 1; 

這玩意就沒這么好算,12左移一位,要把12換成2進制比16換成二進制麻煩,有個更好的方法是拿newCap * 0.75 = 24,你去算也是相等的。那么新的閥值就是24。然后進入循環(huán)

            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    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
                        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);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }

for循環(huán)是循環(huán)數(shù)組,如果當(dāng)前下標(biāo)沒有next,那么這個新數(shù)組的這個下標(biāo)的值就等于舊數(shù)組當(dāng)前下標(biāo)的值。如果這個下標(biāo)的節(jié)點是紅黑樹(就是之前鏈表長度達到7),那就對紅黑樹做操作(這里不講紅黑樹,大概了解就行)。如果都不是(說明當(dāng)前數(shù)組下標(biāo)的節(jié)點是個長度小于7 的鏈表)

                        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);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }

這個操作是,對鏈表進行遍歷,按照e.hash & oldCap這個條件把一條鏈表拆分成high和low兩條鏈表,把low鏈表放到新數(shù)組的當(dāng)前下標(biāo),high鏈表放到新數(shù)組當(dāng)前下標(biāo)+舊長度的下標(biāo)。
舉個例子,舊的長度16,tab[5]的長度為6,它分成長度為2的low和長度為4的high,新數(shù)組tab[5] = low(長度2),tab[21]=high(長度4)
那么我的第三個問題來了,一分二的意圖其實很容易理解,但是為什么要根據(jù)這個條件來分?

6. 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 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((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;
    }

根據(jù)這個key可以用(n - 1) & hash來拿到下標(biāo),put的時候也是這個條件,拿到下標(biāo)之后判斷當(dāng)前這個下標(biāo)的節(jié)點是否為這個key,是的話直接返回它的value,不是的話判斷是不是紅黑樹,不是的話遍歷鏈表來找相同的key,遍歷完還沒有的話就返回空。

7.總結(jié)

(1)主要講了put、get和resize這3個方法
(2)hashmap中有兩個神奇的操作,第一是擴展數(shù)組,第二是把鏈表轉(zhuǎn)成紅黑樹
(3)提出幾個問題,如果有大佬知道,請幫忙解答
為什么獲取hash的時候要與自身的右移動16位做異或運算
為什么根據(jù)key生成下標(biāo)的做法是 (n - 1) & hash
為什么擴展數(shù)組的時候拆分鏈表的條件是e.hash & oldCap

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容