hashmap源碼分析

HashMap的數(shù)據(jù)結(jié)構(gòu)
image.png

從上圖中可以很清楚的看到椰苟,HashMap的數(shù)據(jù)結(jié)構(gòu)是數(shù)組+鏈表+紅黑樹(紅黑樹since JDK1.8)宾抓。我們常把數(shù)組中的每一個(gè)節(jié)點(diǎn)稱為一個(gè)桶碌更。當(dāng)向桶中添加一個(gè)鍵值對(duì)時(shí),首先計(jì)算鍵值對(duì)中key的hash值洞慎,以此確定插入數(shù)組中的位置痛单,但是可能存在同一hash值的元素已經(jīng)被放在數(shù)組同一位置了,這種現(xiàn)象稱為碰撞劲腿,這時(shí)按照尾插法(jdk1.7及以前為頭插法)的方式添加key-value到同一hash值的元素的后面旭绒,鏈表就這樣形成了。當(dāng)鏈表長(zhǎng)度超過(guò)8(TREEIFY_THRESHOLD)時(shí)焦人,鏈表就轉(zhuǎn)換為紅黑樹挥吵。

HashMap是Map接口基于哈希表的實(shí)現(xiàn)。這種實(shí)現(xiàn)提供了所有可選的Map操作花椭,并允許key和value為null(除了HashMap是unsynchronized的和允許使用null外忽匈,HashMap和HashTable大致相同。)矿辽。此類不保證映射的順序丹允,特別是它不保證該順序恒久不變。
此實(shí)現(xiàn)假設(shè)哈希函數(shù)在桶內(nèi)適當(dāng)?shù)胤植荚卮螅瑸榛緦?shí)現(xiàn)(get 和 put)提供了穩(wěn)定的性能雕蔽。迭代 collection 視圖所需的時(shí)間與 HashMap 實(shí)例的“容量”(桶的數(shù)量)及其大小(鍵-值映射關(guān)系數(shù))成比例宾娜。如果遍歷操作很重要批狐,就不要把初始化容量initial capacity設(shè)置得太高(或?qū)⒓虞d因子load factor設(shè)置得太低),否則會(huì)嚴(yán)重降低遍歷的效率前塔。
HashMap有兩個(gè)影響性能的重要參數(shù):初始化容量initial capacity嚣艇、加載因子load factor。容量是哈希表中桶的數(shù)量华弓,初始容量只是哈希表在創(chuàng)建時(shí)的容量食零。加載因子是哈希表在其容量自動(dòng)增加之前可以達(dá)到多滿的一種尺度。initial capacityload factor就是當(dāng)前允許的最大元素?cái)?shù)目该抒,超過(guò)initial capacityload factor之后慌洪,HashMap就會(huì)進(jìn)行rehashed操作來(lái)進(jìn)行擴(kuò)容顶燕,擴(kuò)容后的的容量為之前的兩倍。
通常冈爹,默認(rèn)加載因子 (0.75) 在時(shí)間和空間成本上尋求一種折衷涌攻。加載因子過(guò)高雖然減少了空間開(kāi)銷,但同時(shí)也增加了查詢成本(在大多數(shù) HashMap類的操作中频伤,包括 get 和 put 操作恳谎,都反映了這一點(diǎn))。在設(shè)置初始容量時(shí)應(yīng)該考慮到映射中所需的條目數(shù)及其加載因子憋肖,以便最大限度地減少rehash操作次數(shù)因痛。如果初始容量大于最大條目數(shù)除以加載因子,則不會(huì)發(fā)生rehash 操作岸更。
如果很多映射關(guān)系要存儲(chǔ)在 HashMap 實(shí)例中鸵膏,則相對(duì)于按需執(zhí)行自動(dòng)的 rehash 操作以增大表的容量來(lái)說(shuō),使用足夠大的初始容量創(chuàng)建它將使得映射關(guān)系能更有效地存儲(chǔ)怎炊。
注意谭企,此實(shí)現(xiàn)不是同步的。如果多個(gè)線程同時(shí)訪問(wèn)一個(gè)哈希映射评肆,而其中至少一個(gè)線程從結(jié)構(gòu)上修改了該映射债查,則它必須保持外部同步。(結(jié)構(gòu)上的修改是指添加或刪除一個(gè)或多個(gè)映射關(guān)系的任何操作瓜挽;僅改變與實(shí)例已經(jīng)包含的鍵關(guān)聯(lián)的值不是結(jié)構(gòu)上的修改盹廷。)這一般通過(guò)對(duì)自然封裝該映射的對(duì)象進(jìn)行同步操作來(lái)完成。如果不存在這樣的對(duì)象久橙,則應(yīng)該使用 Collections.synchronizedMap 方法來(lái)“包裝”該映射俄占。最好在創(chuàng)建時(shí)完成這一操作,以防止對(duì)映射進(jìn)行意外的非同步訪問(wèn)剥汤,如下所示:
Map m = Collections.synchronizedMap(new HashMap(…));
由所有此類的“collection 視圖方法”所返回的迭代器都是fail-fast 的:在迭代器創(chuàng)建之后颠放,如果從結(jié)構(gòu)上對(duì)映射進(jìn)行修改,除非通過(guò)迭代器本身的remove方法吭敢,其他任何時(shí)間任何方式的修改,迭代器都將拋出 ConcurrentModificationException暮芭。因此鹿驼,面對(duì)并發(fā)的修改,迭代器很快就會(huì)完全失敗辕宏,而不冒在將來(lái)不確定的時(shí)間發(fā)生任意不確定行為的風(fēng)險(xiǎn)畜晰。
注意,迭代器的快速失敗行為不能得到保證瑞筐,一般來(lái)說(shuō)凄鼻,存在非同步的并發(fā)修改時(shí),不可能作出任何堅(jiān)決的保證】榘觯快速失敗迭代器盡最大努力拋出 ConcurrentModificationException闰非。因此,編寫依賴于此異常的程序的做法是錯(cuò)誤的峭范,正確做法是:迭代器的快速失敗行為應(yīng)該僅用于檢測(cè)bug财松。
此類是 Java Collections Framework 的成員。

靜態(tài)全局變量
/**
     * 默認(rèn)初始化容量纱控,值為16
     * 必須是2的n次冪.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 最大容量, 容量不能超出這個(gè)值辆毡。如果一個(gè)更大的初始化容量在構(gòu)造函數(shù)中被指定,將被MAXIMUM_CAPACITY替換.
     * 必須是2的倍數(shù)甜害。最大容量為1<<30舶掖,即2的30次方。
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

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

    /**
     * 將鏈表轉(zhuǎn)化為紅黑樹的臨界值访锻。
     * 當(dāng)添加一個(gè)元素被添加到有至少TREEIFY_THRESHOLD個(gè)節(jié)點(diǎn)的桶中,桶中鏈表將被轉(zhuǎn)化為樹形結(jié)構(gòu)闹获。
     * 臨界值最小為8
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 恢復(fù)成鏈?zhǔn)浇Y(jié)構(gòu)的桶大小臨界值
     * 小于TREEIFY_THRESHOLD期犬,臨界值最大為6
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 桶可能被轉(zhuǎn)化為樹形結(jié)構(gòu)的最小容量。當(dāng)哈希表的大小超過(guò)這個(gè)閾值避诽,才會(huì)把鏈?zhǔn)浇Y(jié)構(gòu)轉(zhuǎn)化成樹型結(jié)構(gòu)龟虎,否則僅采取擴(kuò)容來(lái)嘗試減少?zèng)_突。
     * 應(yīng)該至少4*TREEIFY_THRESHOLD來(lái)避免擴(kuò)容和樹形結(jié)構(gòu)化之間的沖突沙庐。
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

Node內(nèi)部類

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)部類

// 位于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(int initialCapacity)構(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;
    // 計(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è)元素是樹節(jié)點(diǎn),則調(diào)用樹節(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刁憋,則判斷是否需要樹化滥嘴,因?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è)元素是樹節(jié)點(diǎn),則調(diào)用樹節(jié)點(diǎn)的putTreeVal()尋找元素或插入樹節(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)并判斷是否需要樹化韩脑;
(9)如果找到了對(duì)應(yīng)key的元素氢妈,則判斷是否需要替換舊值,并直接返回舊值段多;
(10)如果插入了元素首量,則數(shù)量加1并判斷是否需要擴(kuò)容;

resize()方法

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è)元素是樹節(jié)點(diǎn),則把這顆樹打散成兩顆樹插入到新桶中去
                    ((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
                else { // preserve order
                    // 如果這個(gè)鏈表不止一個(gè)元素且不是一顆樹
                    // 則分化成兩個(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
                        // 注意:不是(e.hash & (oldCap-1));而是(e.hash & oldCap)
                        // (e.hash & oldCap) 得到的是 元素的在數(shù)組中的位置是否需要移動(dòng),示例如下
                        // 示例1:
                        // e.hash=10 0000 1010
                        // oldCap=16 0001 0000
                        //   &   =0  0000 0000       比較高位的第一位 0
                        //結(jié)論:元素位置在擴(kuò)容后數(shù)組中的位置沒(méi)有發(fā)生改變
                        // 示例2:
                        // e.hash=17 0001 0001
                        // oldCap=16 0001 0000
                        //   &   =1  0001 0000      比較高位的第一位   1
                       //結(jié)論:元素位置在擴(kuò)容后數(shù)組中的位置發(fā)生了改變似舵,新的下標(biāo)位置是原下標(biāo)位置+原數(shù)組長(zhǎng)度
                            // (e.hash & (oldCap-1)) 得到的是下標(biāo)位置,示例如下
                            //   e.hash=10 0000 1010
                            // oldCap-1=15 0000 1111
                            //      &  =10 0000 1010
                            //   e.hash=17 0001 0001
                            // oldCap-1=15 0000 1111
                            //      &  =1  0000 0001
                            //新下標(biāo)位置
                            //   e.hash=17 0001 0001
                            // newCap-1=31 0001 1111    newCap=32
                            //      &  =17 0001 0001    1+oldCap = 1+16
                            //元素在重新計(jì)算hash之后,因?yàn)閚變?yōu)?倍葱峡,那么n-1的mask范圍在高位多1bit(紅色)砚哗,因此新的index就會(huì)發(fā)生這樣的變化:
                            // 0000 0001->0001 0001

                        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(…)方法

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;
    // 找到樹的根節(jié)點(diǎn)
    TreeNode<K, V> root = (parent != null) ? root() : this;
    // 從樹的根節(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值可能相同
            // 在紅黑樹中把同樣hash值的元素存儲(chǔ)在同一顆子樹,這里相當(dāng)于找到了這顆子樹的頂點(diǎn)
            // 從這個(gè)頂點(diǎn)分別遍歷其左右子樹去尋找有沒(méi)有跟待插入的key相同的元素
            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;
            }
            // 如果兩者類型相同颜启,再根據(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;
            // 插入樹節(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值確定在樹的左子樹還是右子樹查找,找到了直接返回暮的;
(5)如果最后沒(méi)有找到則在樹的相應(yīng)位置插入元素笙以,并做平衡;

treeifyBin()方法

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ò)容而不用樹化
        // 因?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)換成樹節(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)始樹化
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

TreeNode.treeify()方法

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)咙咽,其它元素依次插入到樹中再做平衡
        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)其它元素依次插入到紅黑樹中镜廉,再做平衡;
(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è)元素是樹節(jié)點(diǎn),則按樹的方式查找
            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è)元素是樹節(jié)點(diǎn)就按樹的方式來(lái)查找想许,否則按鏈表方式查找;

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

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

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

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è)元素是樹節(jié)點(diǎn)碉哑,則以樹的方式查找節(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)
                // 如果是樹節(jié)點(diǎn),調(diào)用樹的刪除方法(以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)是樹節(jié)點(diǎn)加匈,則按樹的移除節(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)為空溢谤,則需要反樹化(將樹轉(zhuǎn)化為鏈表)
    // 如果需要移動(dòng)節(jié)點(diǎn)且樹的高度比較小瞻凤,則需要反樹化
    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)世杀,下面才是直接刪除紅黑樹的節(jié)點(diǎn)(因?yàn)門reeNode本身即是鏈表節(jié)點(diǎn)又是樹節(jié)點(diǎn))

    // 刪除紅黑樹節(jié)點(diǎn)的大致過(guò)程是尋找右子樹中最小的節(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)也是紅黑樹節(jié)點(diǎn)瞻坝;
(2)先刪除鏈表節(jié)點(diǎn);
(3)再刪除紅黑樹節(jié)點(diǎn)并做平衡杏瞻;

總結(jié)

(1)HashMap是一種散列表所刀,采用(數(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)行樹化斩披,只會(huì)擴(kuò)容;
(5)當(dāng)桶的數(shù)量大于64且單個(gè)桶中元素的數(shù)量大于8時(shí)讹俊,進(jìn)行樹化垦沉;
(6)當(dāng)單個(gè)桶中元素?cái)?shù)量小于6時(shí),進(jìn)行反樹化仍劈;
(7)HashMap是非線程安全的容器厕倍;
(8)HashMap查找添加元素的時(shí)間復(fù)雜度都為O(1);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贩疙,一起剝皮案震驚了整個(gè)濱河市讹弯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌这溅,老刑警劉巖组民,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異芍躏,居然都是意外死亡邪乍,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)庇楞,“玉大人榜配,你說(shuō)我怎么就攤上這事÷郎危” “怎么了蛋褥?”我有些...
    開(kāi)封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)睛驳。 經(jīng)常有香客問(wèn)我烙心,道長(zhǎng),這世上最難降的妖魔是什么乏沸? 我笑而不...
    開(kāi)封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任淫茵,我火速辦了婚禮,結(jié)果婚禮上蹬跃,老公的妹妹穿的比我還像新娘匙瘪。我一直安慰自己,他們只是感情好蝶缀,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布丹喻。 她就那樣靜靜地躺著,像睡著了一般翁都。 火紅的嫁衣襯著肌膚如雪碍论。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天柄慰,我揣著相機(jī)與錄音鳍悠,去河邊找鬼。 笑死先煎,一個(gè)胖子當(dāng)著我的面吹牛贼涩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播薯蝎,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼遥倦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了占锯?” 一聲冷哼從身側(cè)響起袒哥,我...
    開(kāi)封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎消略,沒(méi)想到半個(gè)月后堡称,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡艺演,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年却紧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了桐臊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晓殊,死狀恐怖断凶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巫俺,我是刑警寧澤认烁,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站介汹,受9級(jí)特大地震影響却嗡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嘹承,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一窗价、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赶撰,春花似錦舌镶、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哟楷。三九已至瘤载,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卖擅,已是汗流浹背鸣奔。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惩阶,地道東北人挎狸。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓断楷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親冬筒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 移步數(shù)據(jù)結(jié)構(gòu)--容器匯總(java & Android)由于 HashMap 底層涉及到太多方面土榴,一篇文章總是不能...
    凱玲之戀閱讀 829評(píng)論 0 5
  • 一响牛、HashMap概述 HashMap基于哈希表的Map接口的實(shí)現(xiàn)赫段。此實(shí)現(xiàn)提供所有可選的映射操作,并允許使用nul...
    小陳阿飛閱讀 633評(píng)論 0 2
  • 前言 前面分別分析了常用的 ArrayList 和 LinkedList 的源碼糯笙,前面講了 ArrayList 內(nèi)...
    SmartSean閱讀 443評(píng)論 0 0
  • HashMap采用key/value存儲(chǔ)結(jié)構(gòu),每個(gè)key對(duì)應(yīng)唯一的value坯台,查詢和修改的速度都很快炬丸,能達(dá)到O(1...
    tracy_668閱讀 1,266評(píng)論 0 34
  • 參考文檔:https://www.cnblogs.com/aipiaoborensheng/p/5666005.h...
    鳳非飛閱讀 191評(píng)論 0 1