溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

死磕 java集合之HashMap源碼分析

發(fā)布時(shí)間:2020-07-20 17:34:56 來(lái)源:網(wǎng)絡(luò) 閱讀:250 作者:彤哥讀源碼 欄目:編程語(yǔ)言

歡迎關(guān)注我的公眾號(hào)“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

簡(jiǎn)介

HashMap采用key/value存儲(chǔ)結(jié)構(gòu),每個(gè)key對(duì)應(yīng)唯一的value,查詢和修改的速度都很快,能達(dá)到O(1)的平均時(shí)間復(fù)雜度。它是非線程安全的,且不保證元素存儲(chǔ)的順序;

繼承體系

死磕 java集合之HashMap源碼分析

HashMap實(shí)現(xiàn)了Cloneable,可以被克隆。

HashMap實(shí)現(xiàn)了Serializable,可以被序列化。

HashMap繼承自AbstractMap,實(shí)現(xiàn)了Map接口,具有Map的所有功能。

存儲(chǔ)結(jié)構(gòu)

死磕 java集合之HashMap源碼分析

在Java中,HashMap的實(shí)現(xiàn)采用了(數(shù)組 + 鏈表 + 紅黑樹(shù))的復(fù)雜結(jié)構(gòu),數(shù)組的一個(gè)元素又稱作桶。

在添加元素時(shí),會(huì)根據(jù)hash值算出元素在數(shù)組中的位置,如果該位置沒(méi)有元素,則直接把元素放置在此處,如果該位置有元素了,則把元素以鏈表的形式放置在鏈表的尾部。

當(dāng)一個(gè)鏈表的元素個(gè)數(shù)達(dá)到一定的數(shù)量(且數(shù)組的長(zhǎng)度達(dá)到一定的長(zhǎng)度)后,則把鏈表轉(zhuǎn)化為紅黑樹(shù),從而提高效率。

數(shù)組的查詢效率為O(1),鏈表的查詢效率是O(k),紅黑樹(shù)的查詢效率是O(log k),k為桶中的元素個(gè)數(shù),所以當(dāng)元素?cái)?shù)量非常多的時(shí)候,轉(zhuǎn)化為紅黑樹(shù)能極大地提高效率。

源碼解析

屬性


/**
 * 默認(rèn)的初始容量為16
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

/**
 * 最大的容量為2的30次方
 */
static final int MAXIMUM_CAPACITY = 1 << 30;

/**
 * 默認(rèn)的裝載因子
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**
 * 當(dāng)一個(gè)桶中的元素個(gè)數(shù)大于等于8時(shí)進(jìn)行樹(shù)化
 */
static final int TREEIFY_THRESHOLD = 8;

/**
 * 當(dāng)一個(gè)桶中的元素個(gè)數(shù)小于等于6時(shí)把樹(shù)轉(zhuǎn)化為鏈表
 */
static final int UNTREEIFY_THRESHOLD = 6;

/**
 * 當(dāng)桶的個(gè)數(shù)達(dá)到64的時(shí)候才進(jìn)行樹(shù)化
 */
static final int MIN_TREEIFY_CAPACITY = 64;

/**
 * 數(shù)組,又叫作桶(bucket)
 */
transient Node<K,V>[] table;

/**
 * 作為entrySet()的緩存
 */
transient Set<Map.Entry<K,V>> entrySet;

/**
 * 元素的數(shù)量
 */
transient int size;

/**
 * 修改次數(shù),用于在迭代的時(shí)候執(zhí)行快速失敗策略
 */
transient int modCount;

/**
 * 當(dāng)桶的使用數(shù)量達(dá)到多少時(shí)進(jìn)行擴(kuò)容,threshold = capacity * loadFactor
 */
int threshold;

/**
 * 裝載因子
 */
final float loadFactor;

(1)容量

容量為數(shù)組的長(zhǎng)度,亦即桶的個(gè)數(shù),默認(rèn)為16,最大為2的30次方,當(dāng)容量達(dá)到64時(shí)才可以樹(shù)化。

(2)裝載因子

裝載因子用來(lái)計(jì)算容量達(dá)到多少時(shí)才進(jìn)行擴(kuò)容,默認(rèn)裝載因子為0.75。

(3)樹(shù)化

樹(shù)化,當(dāng)容量達(dá)到64且鏈表的長(zhǎng)度達(dá)到8時(shí)進(jìn)行樹(shù)化,當(dāng)鏈表的長(zhǎng)度小于6時(shí)反樹(shù)化。

Node內(nèi)部類

Node是一個(gè)典型的單鏈表節(jié)點(diǎn),其中,hash用來(lái)存儲(chǔ)key計(jì)算得來(lái)的hash值。

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

TreeNode內(nèi)部類

這是一個(gè)神奇的類,它繼承自LinkedHashMap中的Entry類,關(guān)于LInkedHashMap.Entry這個(gè)類我們后面再講。

TreeNode是一個(gè)典型的樹(shù)型節(jié)點(diǎn),其中,prev是鏈表中的節(jié)點(diǎn),用于在刪除元素的時(shí)候可以快速找到它的前置節(jié)點(diǎn)。

// 位于HashMap中
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
}

// 位于LinkedHashMap中,典型的雙向鏈表節(jié)點(diǎn)
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

HashMap()構(gòu)造方法

空參構(gòu)造方法,全部使用默認(rèn)值。

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

HashMap(int initialCapacity)構(gòu)造方法

調(diào)用HashMap(int initialCapacity, float loadFactor)構(gòu)造方法,傳入默認(rèn)裝載因子。

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

HashMap(int initialCapacity)構(gòu)造方法

判斷傳入的初始容量和裝載因子是否合法,并計(jì)算擴(kuò)容門檻,擴(kuò)容門檻為傳入的初始容量往上取最近的2的n次方。

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;
    // 計(jì)算擴(kuò)容門檻
    this.threshold = tableSizeFor(initialCapacity);
}

static final int tableSizeFor(int cap) {
    // 擴(kuò)容門檻為傳入的初始容量往上取最近的2的n次方
    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;
}

put(K key, V value)方法

添加元素的入口。

public V put(K key, V value) {
    // 調(diào)用hash(key)計(jì)算出key的hash值
    return putVal(hash(key), key, value, false, true);
}

static final int hash(Object key) {
    int h;
    // 如果key為null,則hash值為0,否則調(diào)用key的hashCode()方法
    // 并讓高16位與整個(gè)hash異或,這樣做是為了使計(jì)算出的hash更分散
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K, V>[] tab;
    Node<K, V> p;
    int n, i;
    // 如果桶的數(shù)量為0,則初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        // 調(diào)用resize()初始化
        n = (tab = resize()).length;
    // (n - 1) & hash 計(jì)算元素在哪個(gè)桶中
    // 如果這個(gè)桶中還沒(méi)有元素,則把這個(gè)元素放在桶中的第一個(gè)位置
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 新建一個(gè)節(jié)點(diǎn)放在桶中
        tab[i] = newNode(hash, key, value, null);
    else {
        // 如果桶中已經(jīng)有元素存在了
        Node<K, V> e;
        K k;
        // 如果桶中第一個(gè)元素的key與待插入元素的key相同,保存到e中用于后續(xù)修改value值
        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            // 如果第一個(gè)元素是樹(shù)節(jié)點(diǎn),則調(diào)用樹(shù)節(jié)點(diǎn)的putTreeVal插入元素
            e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
        else {
            // 遍歷這個(gè)桶對(duì)應(yīng)的鏈表,binCount用于存儲(chǔ)鏈表中元素的個(gè)數(shù)
            for (int binCount = 0; ; ++binCount) {
                // 如果鏈表遍歷完了都沒(méi)有找到相同key的元素,說(shuō)明該key對(duì)應(yīng)的元素不存在,則在鏈表最后插入一個(gè)新節(jié)點(diǎn)
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 如果插入新節(jié)點(diǎn)后鏈表長(zhǎng)度大于8,則判斷是否需要樹(shù)化,因?yàn)榈谝粋€(gè)元素沒(méi)有加到binCount中,所以這里-1
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 如果待插入的key在鏈表中找到了,則退出循環(huán)
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 如果找到了對(duì)應(yīng)key的元素
        if (e != null) { // existing mapping for key
            // 記錄下舊值
            V oldValue = e.value;
            // 判斷是否需要替換舊值
            if (!onlyIfAbsent || oldValue == null)
                // 替換舊值為新值
                e.value = value;
            // 在節(jié)點(diǎn)被訪問(wèn)后做點(diǎn)什么事,在LinkedHashMap中用到
            afterNodeAccess(e);
            // 返回舊值
            return oldValue;
        }
    }
    // 到這里了說(shuō)明沒(méi)有找到元素
    // 修改次數(shù)加1
    ++modCount;
    // 元素?cái)?shù)量加1,判斷是否需要擴(kuò)容
    if (++size > threshold)
        // 擴(kuò)容
        resize();
    // 在節(jié)點(diǎn)插入后做點(diǎn)什么事,在LinkedHashMap中用到
    afterNodeInsertion(evict);
    // 沒(méi)找到元素返回null
    return null;
}

(1)計(jì)算key的hash值;

(2)如果桶(數(shù)組)數(shù)量為0,則初始化桶;

(3)如果key所在的桶沒(méi)有元素,則直接插入;

(4)如果key所在的桶中的第一個(gè)元素的key與待插入的key相同,說(shuō)明找到了元素,轉(zhuǎn)后續(xù)流程(9)處理;

(5)如果第一個(gè)元素是樹(shù)節(jié)點(diǎn),則調(diào)用樹(shù)節(jié)點(diǎn)的putTreeVal()尋找元素或插入樹(shù)節(jié)點(diǎn);

(6)如果不是以上三種情況,則遍歷桶對(duì)應(yīng)的鏈表查找key是否存在于鏈表中;

(7)如果找到了對(duì)應(yīng)key的元素,則轉(zhuǎn)后續(xù)流程(9)處理;

(8)如果沒(méi)找到對(duì)應(yīng)key的元素,則在鏈表最后插入一個(gè)新節(jié)點(diǎn)并判斷是否需要樹(shù)化;

(9)如果找到了對(duì)應(yīng)key的元素,則判斷是否需要替換舊值,并直接返回舊值;

(10)如果插入了元素,則數(shù)量加1并判斷是否需要擴(kuò)容;

resize()方法

擴(kuò)容方法。

final Node<K, V>[] resize() {
    // 舊數(shù)組
    Node<K, V>[] oldTab = table;
    // 舊容量
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // 舊擴(kuò)容門檻
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            // 如果舊容量達(dá)到了最大容量,則不再進(jìn)行擴(kuò)容
            threshold = Integer.MAX_VALUE;
            return oldTab;
        } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 如果舊容量的兩倍小于最大容量并且舊容量大于默認(rèn)初始容量(16),則容量擴(kuò)大為兩部,擴(kuò)容門檻也擴(kuò)大為兩倍
            newThr = oldThr << 1; // double threshold
    } else if (oldThr > 0) // initial capacity was placed in threshold
        // 使用非默認(rèn)構(gòu)造方法創(chuàng)建的map,第一次插入元素會(huì)走到這里
        // 如果舊容量為0且舊擴(kuò)容門檻大于0,則把新容量賦值為舊門檻
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        // 調(diào)用默認(rèn)構(gòu)造方法創(chuàng)建的map,第一次插入元素會(huì)走到這里
        // 如果舊容量舊擴(kuò)容門檻都是0,說(shuō)明還未初始化過(guò),則初始化容量為默認(rèn)容量,擴(kuò)容門檻為默認(rèn)容量*默認(rèn)裝載因子
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        // 如果新擴(kuò)容門檻為0,則計(jì)算為容量*裝載因子,但不能超過(guò)最大容量
        float ft = (float) newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
                (int) ft : Integer.MAX_VALUE);
    }
    // 賦值擴(kuò)容門檻為新門檻
    threshold = newThr;
    // 新建一個(gè)新容量的數(shù)組
    @SuppressWarnings({"rawtypes", "unchecked"})
    Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
    // 把桶賦值為新數(shù)組
    table = newTab;
    // 如果舊數(shù)組不為空,則搬移元素
    if (oldTab != null) {
        // 遍歷舊數(shù)組
        for (int j = 0; j < oldCap; ++j) {
            Node<K, V> e;
            // 如果桶中第一個(gè)元素不為空,賦值給e
            if ((e = oldTab[j]) != null) {
                // 清空舊桶,便于GC回收  
                oldTab[j] = null;
                // 如果這個(gè)桶中只有一個(gè)元素,則計(jì)算它在新桶中的位置并把它搬移到新桶中
                // 因?yàn)槊看味紨U(kuò)容兩倍,所以這里的第一個(gè)元素搬移到新桶的時(shí)候新桶肯定還沒(méi)有元素
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    // 如果第一個(gè)元素是樹(shù)節(jié)點(diǎn),則把這顆樹(shù)打散成兩顆樹(shù)插入到新桶中去
                    ((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
                else { // preserve order
                    // 如果這個(gè)鏈表不止一個(gè)元素且不是一顆樹(shù)
                    // 則分化成兩個(gè)鏈表插入到新的桶中去
                    // 比如,假如原來(lái)容量為4,3、7、11、15這四個(gè)元素都在三號(hào)桶中
                    // 現(xiàn)在擴(kuò)容到8,則3和11還是在三號(hào)桶,7和15要搬移到七號(hào)桶中去
                    // 也就是分化成了兩個(gè)鏈表
                    Node<K, V> loHead = null, loTail = null;
                    Node<K, V> hiHead = null, hiTail = null;
                    Node<K, V> next;
                    do {
                        next = e.next;
                        // (e.hash & oldCap) == 0的元素放在低位鏈表中
                        // 比如,3 & 4 == 0
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        } else {
                            // (e.hash & oldCap) != 0的元素放在高位鏈表中
                            // 比如,7 & 4 != 0
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    // 遍歷完成分化成兩個(gè)鏈表了
                    // 低位鏈表在新桶中的位置與舊桶一樣(即3和11還在三號(hào)桶中)
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    // 高位鏈表在新桶中的位置正好是原來(lái)的位置加上舊容量(即7和15搬移到七號(hào)桶了)
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

(1)如果使用是默認(rèn)構(gòu)造方法,則第一次插入元素時(shí)初始化為默認(rèn)值,容量為16,擴(kuò)容門檻為12;

(2)如果使用的是非默認(rèn)構(gòu)造方法,則第一次插入元素時(shí)初始化容量等于擴(kuò)容門檻,擴(kuò)容門檻在構(gòu)造方法里等于傳入容量向上最近的2的n次方;

(3)如果舊容量大于0,則新容量等于舊容量的2倍,但不超過(guò)最大容量2的30次方,新擴(kuò)容門檻為舊擴(kuò)容門檻的2倍;

(4)創(chuàng)建一個(gè)新容量的桶;

(5)搬移元素,原鏈表分化成兩個(gè)鏈表,低位鏈表存儲(chǔ)在原來(lái)桶的位置,高位鏈表搬移到原來(lái)桶的位置加舊容量的位置;

TreeNode.putTreeVal(...)方法

插入元素到紅黑樹(shù)中的方法。

final TreeNode<K, V> putTreeVal(HashMap<K, V> map, Node<K, V>[] tab,
                                int h, K k, V v) {
    Class<?> kc = null;
    // 標(biāo)記是否找到這個(gè)key的節(jié)點(diǎn)
    boolean searched = false;
    // 找到樹(shù)的根節(jié)點(diǎn)
    TreeNode<K, V> root = (parent != null) ? root() : this;
    // 從樹(shù)的根節(jié)點(diǎn)開(kāi)始遍歷
    for (TreeNode<K, V> p = root; ; ) {
        // dir=direction,標(biāo)記是在左邊還是右邊
        // ph=p.hash,當(dāng)前節(jié)點(diǎn)的hash值
        int dir, ph;
        // pk=p.key,當(dāng)前節(jié)點(diǎn)的key值
        K pk;
        if ((ph = p.hash) > h) {
            // 當(dāng)前hash比目標(biāo)hash大,說(shuō)明在左邊
            dir = -1;
        }
        else if (ph < h)
            // 當(dāng)前hash比目標(biāo)hash小,說(shuō)明在右邊
            dir = 1;
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            // 兩者h(yuǎn)ash相同且key相等,說(shuō)明找到了節(jié)點(diǎn),直接返回該節(jié)點(diǎn)
            // 回到putVal()中判斷是否需要修改其value值
            return p;
        else if ((kc == null &&
                // 如果k是Comparable的子類則返回其真實(shí)的類,否則返回null
                (kc = comparableClassFor(k)) == null) ||
                // 如果k和pk不是同樣的類型則返回0,否則返回兩者比較的結(jié)果
                (dir = compareComparables(kc, k, pk)) == 0) {
            // 這個(gè)條件表示兩者h(yuǎn)ash相同但是其中一個(gè)不是Comparable類型或者兩者類型不同
            // 比如key是Object類型,這時(shí)可以傳String也可以傳Integer,兩者h(yuǎn)ash值可能相同
            // 在紅黑樹(shù)中把同樣hash值的元素存儲(chǔ)在同一顆子樹(shù),這里相當(dāng)于找到了這顆子樹(shù)的頂點(diǎn)
            // 從這個(gè)頂點(diǎn)分別遍歷其左右子樹(shù)去尋找有沒(méi)有跟待插入的key相同的元素
            if (!searched) {
                TreeNode<K, V> q, ch;
                searched = true;
                // 遍歷左右子樹(shù)找到了直接返回
                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;
            }
            // 如果兩者類型相同,再根據(jù)它們的內(nèi)存地址計(jì)算hash值進(jìn)行比較
            dir = tieBreakOrder(k, pk);
        }

        TreeNode<K, V> xp = p;
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
            // 如果最后確實(shí)沒(méi)找到對(duì)應(yīng)key的元素,則新建一個(gè)節(jié)點(diǎn)
            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;
            // 插入樹(shù)節(jié)點(diǎn)后平衡
            // 把root節(jié)點(diǎn)移動(dòng)到鏈表的第一個(gè)節(jié)點(diǎn)
            moveRootToFront(tab, balanceInsertion(root, x));
            return null;
        }
    }
}

(1)尋找根節(jié)點(diǎn);

(2)從根節(jié)點(diǎn)開(kāi)始查找;

(3)比較hash值及key值,如果都相同,直接返回,在putVal()方法中決定是否要替換value值;

(4)根據(jù)hash值及key值確定在樹(shù)的左子樹(shù)還是右子樹(shù)查找,找到了直接返回;

(5)如果最后沒(méi)有找到則在樹(shù)的相應(yīng)位置插入元素,并做平衡;

treeifyBin()方法

如果插入元素后鏈表的長(zhǎng)度大于等于8則判斷是否需要樹(shù)化。

final void treeifyBin(Node<K, V>[] tab, int hash) {
    int n, index;
    Node<K, V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        // 如果桶數(shù)量小于64,直接擴(kuò)容而不用樹(shù)化
        // 因?yàn)閿U(kuò)容之后,鏈表會(huì)分化成兩個(gè)鏈表,達(dá)到減少元素的作用
        // 當(dāng)然也不一定,比如容量為4,里面存的全是除以4余數(shù)等于3的元素
        // 這樣即使擴(kuò)容也無(wú)法減少鏈表的長(zhǎng)度
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K, V> hd = null, tl = null;
        // 把所有節(jié)點(diǎn)換成樹(shù)節(jié)點(diǎn)
        do {
            TreeNode<K, V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        // 如果進(jìn)入過(guò)上面的循環(huán),則從頭節(jié)點(diǎn)開(kāi)始樹(shù)化
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

TreeNode.treeify()方法

真正樹(shù)化的方法。

final void treeify(Node<K, V>[] tab) {
    TreeNode<K, V> root = null;
    for (TreeNode<K, V> x = this, next; x != null; x = next) {
        next = (TreeNode<K, V>) x.next;
        x.left = x.right = null;
        // 第一個(gè)元素作為根節(jié)點(diǎn)且為黑節(jié)點(diǎn),其它元素依次插入到樹(shù)中再做平衡
        if (root == null) {
            x.parent = null;
            x.red = false;
            root = x;
        } else {
            K k = x.key;
            int h = x.hash;
            Class<?> kc = null;
            // 從根節(jié)點(diǎn)查找元素插入的位置
            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);

                // 如果最后沒(méi)找到元素,則插入
                TreeNode<K, V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    x.parent = xp;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    // 插入后平衡,默認(rèn)插入的是紅節(jié)點(diǎn),在balanceInsertion()方法里
                    root = balanceInsertion(root, x);
                    break;
                }
            }
        }
    }
    // 把根節(jié)點(diǎn)移動(dòng)到鏈表的頭節(jié)點(diǎn),因?yàn)榻?jīng)過(guò)平衡之后原來(lái)的第一個(gè)元素不一定是根節(jié)點(diǎn)了
    moveRootToFront(tab, root);
}

(1)從鏈表的第一個(gè)元素開(kāi)始遍歷;

(2)將第一個(gè)元素作為根節(jié)點(diǎn);

(3)其它元素依次插入到紅黑樹(shù)中,再做平衡;

(4)將根節(jié)點(diǎn)移到鏈表第一元素的位置(因?yàn)槠胶獾臅r(shí)候根節(jié)點(diǎn)會(huì)改變);

get(Object key)方法

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;
    // 如果桶的數(shù)量大于0并且待查找的key所在的桶的第一個(gè)元素不為空
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
        // 檢查第一個(gè)元素是不是要查的元素,如果是直接返回
        if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            // 如果第一個(gè)元素是樹(shù)節(jié)點(diǎn),則按樹(shù)的方式查找
            if (first instanceof TreeNode)
                return ((TreeNode<K, V>) first).getTreeNode(hash, key);

            // 否則就遍歷整個(gè)鏈表查找該元素
            do {
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

(1)計(jì)算key的hash值;

(2)找到key所在的桶及其第一個(gè)元素;

(3)如果第一個(gè)元素的key等于待查找的key,直接返回;

(4)如果第一個(gè)元素是樹(shù)節(jié)點(diǎn)就按樹(shù)的方式來(lái)查找,否則按鏈表方式查找;

TreeNode.getTreeNode(int h, Object k)方法

final TreeNode<K, V> getTreeNode(int h, Object k) {
    // 從樹(shù)的根節(jié)點(diǎn)開(kāi)始查找
    return ((parent != null) ? root() : this).find(h, k, null);
}

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)
            // 左子樹(shù)
            p = pl;
        else if (ph < h)
            // 右子樹(shù)
            p = pr;
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            // 找到了直接返回
            return p;
        else if (pl == null)
            // hash相同但key不同,左子樹(shù)為空查右子樹(shù)
            p = pr;
        else if (pr == null)
            // 右子樹(shù)為空查左子樹(shù)
            p = pl;
        else if ((kc != null ||
                (kc = comparableClassFor(k)) != null) &&
                (dir = compareComparables(kc, k, pk)) != 0)
            // 通過(guò)compare方法比較key值的大小決定使用左子樹(shù)還是右子樹(shù)
            p = (dir < 0) ? pl : pr;
        else if ((q = pr.find(h, k, kc)) != null)
            // 如果以上條件都不通過(guò),則嘗試在右子樹(shù)查找
            return q;
        else
            // 都沒(méi)找到就在左子樹(shù)查找
            p = pl;
    } while (p != null);
    return null;
}

經(jīng)典二叉查找樹(shù)的查找過(guò)程,先根據(jù)hash值比較,再根據(jù)key值比較決定是查左子樹(shù)還是右子樹(shù)。

remove(Object key)方法

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;
    // 如果桶的數(shù)量大于0且待刪除的元素所在的桶的第一個(gè)元素不為空
    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))))
            // 如果第一個(gè)元素正好就是要找的元素,賦值給node變量后續(xù)刪除使用
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode)
                // 如果第一個(gè)元素是樹(shù)節(jié)點(diǎn),則以樹(shù)的方式查找節(jié)點(diǎn)
                node = ((TreeNode<K, V>) p).getTreeNode(hash, key);
            else {
                // 否則遍歷整個(gè)鏈表查找元素
                do {
                    if (e.hash == hash &&
                            ((k = e.key) == key ||
                                    (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        // 如果找到了元素,則看參數(shù)是否需要匹配value值,如果不需要匹配直接刪除,如果需要匹配則看value值是否與傳入的value相等
        if (node != null && (!matchValue || (v = node.value) == value ||
                (value != null && value.equals(v)))) {
            if (node instanceof TreeNode)
                // 如果是樹(shù)節(jié)點(diǎn),調(diào)用樹(shù)的刪除方法(以node調(diào)用的,是刪除自己)
                ((TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
            else if (node == p)
                // 如果待刪除的元素是第一個(gè)元素,則把第二個(gè)元素移到第一的位置
                tab[index] = node.next;
            else
                // 否則刪除node節(jié)點(diǎn)
                p.next = node.next;
            ++modCount;
            --size;
            // 刪除節(jié)點(diǎn)后置處理
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

(1)先查找元素所在的節(jié)點(diǎn);

(2)如果找到的節(jié)點(diǎn)是樹(shù)節(jié)點(diǎn),則按樹(shù)的移除節(jié)點(diǎn)處理;

(3)如果找到的節(jié)點(diǎn)是桶中的第一個(gè)節(jié)點(diǎn),則把第二個(gè)節(jié)點(diǎn)移到第一的位置;

(4)否則按鏈表刪除節(jié)點(diǎn)處理;

(5)修改size,調(diào)用移除節(jié)點(diǎn)后置處理等;

TreeNode.removeTreeNode(...)方法

final void removeTreeNode(HashMap<K, V> map, Node<K, V>[] tab,
                          boolean movable) {
    int n;
    // 如果桶的數(shù)量為0直接返回
    if (tab == null || (n = tab.length) == 0)
        return;
    // 節(jié)點(diǎn)在桶中的索引
    int index = (n - 1) & hash;
    // 第一個(gè)節(jié)點(diǎn),根節(jié)點(diǎn),根左子節(jié)點(diǎn)
    TreeNode<K, V> first = (TreeNode<K, V>) tab[index], root = first, rl;
    // 后繼節(jié)點(diǎn),前置節(jié)點(diǎn)
    TreeNode<K, V> succ = (TreeNode<K, V>) next, pred = prev;

    if (pred == null)
        // 如果前置節(jié)點(diǎn)為空,說(shuō)明當(dāng)前節(jié)點(diǎn)是根節(jié)點(diǎn),則把后繼節(jié)點(diǎn)賦值到第一個(gè)節(jié)點(diǎn)的位置,相當(dāng)于刪除了當(dāng)前節(jié)點(diǎn)
        tab[index] = first = succ;
    else
        // 否則把前置節(jié)點(diǎn)的下個(gè)節(jié)點(diǎn)設(shè)置為當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn),相當(dāng)于刪除了當(dāng)前節(jié)點(diǎn)
        pred.next = succ;

    // 如果后繼節(jié)點(diǎn)不為空,則讓后繼節(jié)點(diǎn)的前置節(jié)點(diǎn)指向當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn),相當(dāng)于刪除了當(dāng)前節(jié)點(diǎn)
    if (succ != null)
        succ.prev = pred;

    // 如果第一個(gè)節(jié)點(diǎn)為空,說(shuō)明沒(méi)有后繼節(jié)點(diǎn)了,直接返回
    if (first == null)
        return;

    // 如果根節(jié)點(diǎn)的父節(jié)點(diǎn)不為空,則重新查找父節(jié)點(diǎn)
    if (root.parent != null)
        root = root.root();

    // 如果根節(jié)點(diǎn)為空,則需要反樹(shù)化(將樹(shù)轉(zhuǎn)化為鏈表)
    // 如果需要移動(dòng)節(jié)點(diǎn)且樹(shù)的高度比較小,則需要反樹(shù)化
    if (root == null
            || (movable
            && (root.right == null
            || (rl = root.left) == null
            || rl.left == null))) {
        tab[index] = first.untreeify(map);  // too small
        return;
    }

    // 分割線,以上都是刪除鏈表中的節(jié)點(diǎn),下面才是直接刪除紅黑樹(shù)的節(jié)點(diǎn)(因?yàn)門reeNode本身即是鏈表節(jié)點(diǎn)又是樹(shù)節(jié)點(diǎn))

    // 刪除紅黑樹(shù)節(jié)點(diǎn)的大致過(guò)程是尋找右子樹(shù)中最小的節(jié)點(diǎn)放到刪除節(jié)點(diǎn)的位置,然后做平衡,此處不過(guò)多注釋
    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) // find successor
            s = sl;
        boolean c = s.red;
        s.red = p.red;
        p.red = c; // swap colors
        TreeNode<K, V> sr = s.right;
        TreeNode<K, V> pp = p.parent;
        if (s == pr) { // p was s's direct parent
            p.parent = s;
            s.right = p;
        } else {
            TreeNode<K, V> sp = s.parent;
            if ((p.parent = sp) != null) {
                if (s == sp.left)
                    sp.left = p;
                else
                    sp.right = p;
            }
            if ((s.right = pr) != null)
                pr.parent = s;
        }
        p.left = null;
        if ((p.right = sr) != null)
            sr.parent = p;
        if ((s.left = pl) != null)
            pl.parent = s;
        if ((s.parent = pp) == null)
            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)
        replacement = pl;
    else if (pr != null)
        replacement = pr;
    else
        replacement = p;
    if (replacement != p) {
        TreeNode<K, V> pp = replacement.parent = p.parent;
        if (pp == null)
            root = replacement;
        else if (p == pp.left)
            pp.left = replacement;
        else
            pp.right = replacement;
        p.left = p.right = p.parent = null;
    }

    TreeNode<K, V> r = p.red ? root : balanceDeletion(root, replacement);

    if (replacement == p) {  // detach
        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);
}

(1)TreeNode本身既是鏈表節(jié)點(diǎn)也是紅黑樹(shù)節(jié)點(diǎn);

(2)先刪除鏈表節(jié)點(diǎn);

(3)再刪除紅黑樹(shù)節(jié)點(diǎn)并做平衡;

總結(jié)

(1)HashMap是一種散列表,采用(數(shù)組 + 鏈表 + 紅黑樹(shù))的存儲(chǔ)結(jié)構(gòu);

(2)HashMap的默認(rèn)初始容量為16(1<<4),默認(rèn)裝載因子為0.75f,容量總是2的n次方;

(3)HashMap擴(kuò)容時(shí)每次容量變?yōu)樵瓉?lái)的兩倍;

(4)當(dāng)桶的數(shù)量小于64時(shí)不會(huì)進(jìn)行樹(shù)化,只會(huì)擴(kuò)容;

(5)當(dāng)桶的數(shù)量大于64且單個(gè)桶中元素的數(shù)量大于8時(shí),進(jìn)行樹(shù)化;

(6)當(dāng)單個(gè)桶中元素?cái)?shù)量小于6時(shí),進(jìn)行反樹(shù)化;

(7)HashMap是非線程安全的容器;

(8)HashMap查找添加元素的時(shí)間復(fù)雜度都為O(1);

帶詳細(xì)注釋的源碼地址

帶詳細(xì)注釋的源碼地址

彩蛋

紅黑樹(shù)知多少?

紅黑樹(shù)具有以下5種性質(zhì):

(1)節(jié)點(diǎn)是紅色或黑色。

(2)根節(jié)點(diǎn)是黑色。

(3)每個(gè)葉節(jié)點(diǎn)(NIL節(jié)點(diǎn),空節(jié)點(diǎn))是黑色的。

(4)每個(gè)紅色節(jié)點(diǎn)的兩個(gè)子節(jié)點(diǎn)都是黑色。(從每個(gè)葉子到根的所有路徑上不能有兩個(gè)連續(xù)的紅色節(jié)點(diǎn))

(5)從任一節(jié)點(diǎn)到其每個(gè)葉子的所有路徑都包含相同數(shù)目的黑色節(jié)點(diǎn)。

紅黑樹(shù)的時(shí)間復(fù)雜度為O(log n),與樹(shù)的高度成正比。

紅黑樹(shù)每次的插入、刪除操作都需要做平衡,平衡時(shí)有可能會(huì)改變根節(jié)點(diǎn)的位置,顏色轉(zhuǎn)換,左旋,右旋等。


歡迎關(guān)注我的公眾號(hào)“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

死磕 java集合之HashMap源碼分析

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI