自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

阿里面試:HashMap是如何實現(xiàn)擴(kuò)容?哪三步?

開發(fā) 前端
為了盡可能地減少擴(kuò)容操作的次數(shù),通常會將負(fù)載因子設(shè)置為一個較小的值,例如0.75,以保證哈希表的容量能夠滿足存儲需求,同時又不會造成太多的空間浪費(fèi)。

1.達(dá)到閾值開始擴(kuò)容

如下圖所示:

圖片圖片

HashMap的擴(kuò)容機(jī)制是當(dāng)HashMap中的元素個數(shù)超過了負(fù)載因子(loadFactor)與初始容量(initialCapacity)的乘積時,就會觸發(fā)擴(kuò)容機(jī)制。

默認(rèn)的構(gòu)造函數(shù)指定了擴(kuò)容因子:0.75, 默認(rèn)容量是16,如下所示:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 相當(dāng)于16
static final float DEFAULT_LOAD_FACTOR = 0.75f;


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

元素數(shù)量達(dá)到了閾值(即負(fù)載因子 * 桶的數(shù)量)時,會觸發(fā)擴(kuò)容操作,也就是說第1次擴(kuò)容的動作會在元素個數(shù)達(dá)到12(16*0.75)的時候觸發(fā)擴(kuò)容。

為了盡可能地減少擴(kuò)容操作的次數(shù),通常會將負(fù)載因子設(shè)置為一個較小的值,例如0.75,以保證哈希表的容量能夠滿足存儲需求,同時又不會造成太多的空間浪費(fèi)。

2.開始擴(kuò)容執(zhí)行

HashMap通過resize()方法進(jìn)行擴(kuò)容,容量規(guī)則為2的冪次。

如下所示:

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    //以前的容量大于0,也就是hashMap中已經(jīng)有元素了,或者new對象的時候設(shè)置了初始容量
    if (oldCap > 0) {
        //如果以前的容量大于限制的最大容量1<<30,則設(shè)置臨界值為int的最大值2^31-1
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        /**
         * 如果以前容量的2倍小于限制的最大容量,同時大于或等于默認(rèn)的容量16,則設(shè)置臨界值為以前臨界值的2
         * 倍,因為threshold = loadFactor*capacity,capacity擴(kuò)大了2倍,loadFactor不變,
         * threshold自然也擴(kuò)大2倍。
         */
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    /**
     * 在HashMap構(gòu)造器Hash(int initialCapacity, float loadFactor)中有一句代碼,this.threshold    
     * = tableSizeFor(initialCapacity), 表示在調(diào)用構(gòu)造器時,默認(rèn)是將初始容量暫時賦值給了
     * threshold臨界值,因此此處相當(dāng)于將上一次的初始容量賦值給了新的容量。什么情況下會執(zhí)行到這句?當(dāng)調(diào)用    
     * 了HashMap(int initialCapacity)構(gòu)造器,還沒有添加元素時
     */
    else if (oldThr > 0) 
        newCap = oldThr;
    /**
     * 調(diào)用了默認(rèn)構(gòu)造器,初始容量沒有設(shè)置,因此使用默認(rèn)容量DEFAULT_INITIAL_CAPACITY(16),臨界值
     * 就是16*0.75
     */
    else {               
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    //對臨界值做判斷,確保其不為0,因為在上面第二種情況(oldThr > 0),并沒有計算newThr
    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"})
    /**構(gòu)造新表,初始化表中數(shù)據(jù)*/
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    //將剛創(chuàng)建的新表賦值給table
    table = newTab;
    if (oldTab != null) {
        //遍歷將原來table中的數(shù)據(jù)放到擴(kuò)容后的新表中來
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                //沒有鏈表Node節(jié)點(diǎn),直接放到新的table中下標(biāo)為【e.hash & (newCap - 1)】位置即可
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                //如果是treeNode節(jié)點(diǎn),則樹上的節(jié)點(diǎn)放到newTab中
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                //如果e后面還有鏈表節(jié)點(diǎn),則遍歷e所在的鏈表,
                else { // 保證順序
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        //記錄下一個節(jié)點(diǎn)
                        next = e.next;
                        /**
                         * newTab的容量是以前舊表容量的兩倍,因為數(shù)組table下標(biāo)并不是根據(jù)循環(huán)逐步遞增
                         * 的,而是通過(table.length-1)& hash計算得到,因此擴(kuò)容后,存放的位置就
                         * 可能發(fā)生變化,那么到底發(fā)生怎樣的變化呢,就是由下面的算法得到.
                         *
                         * 通過e.hash & oldCap來判斷節(jié)點(diǎn)位置通過再次hash算法后,是否會發(fā)生改變,如
                         * 果為0表示不會發(fā)生改變,如果為1表示會發(fā)生改變。到底怎么理解呢,舉個例子:
                         * e.hash = 13 二進(jìn)制:0000 1101
                         * oldCap = 32 二進(jìn)制:0001 0000
                         *  &運(yùn)算:0  二進(jìn)制:0000 0000
                         * 結(jié)論:元素位置在擴(kuò)容后不會發(fā)生改變
                         */
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        /**
                         * e.hash = 18 二進(jìn)制:0001 0010
                         * oldCap = 32 二進(jìn)制:0001 0000
                         * &運(yùn)算:32 二進(jìn)制:0001 0000
                         * 結(jié)論:元素位置在擴(kuò)容后會發(fā)生改變,那么如何改變呢?
                         * newCap = 64 二進(jìn)制:0010 0000
                         * 通過(newCap-1)&hash
                         * 即0001 1111 & 0001 0010 得0001 0010,32+2 = 34
                         */
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        /**
                         * 若(e.hash & oldCap) == 0,下標(biāo)不變,將原表某個下標(biāo)的元素放到擴(kuò)容表同樣
                         * 下標(biāo)的位置上
                         */
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        /**
                         * 若(e.hash & oldCap) != 0,將原表某個下標(biāo)的元素放到擴(kuò)容表中
                         * [下標(biāo)+增加的擴(kuò)容量]的位置上
                         */
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

擴(kuò)容會將HashMap的容量(即桶的數(shù)量)翻倍,擴(kuò)容的大小是原來的2倍,并重新計算每個元素在新桶中的位置。

3.新建哈希表存儲擴(kuò)容

擴(kuò)容操作需要創(chuàng)建一個新的哈希表,并將舊哈希表中的元素重新分配到新哈希表的桶中。

重新分配元素時,HashMap會對每個元素的哈希值取模得到一個新的桶位置,并將元素插入到新的桶中。

擴(kuò)容操作完成后,HashMap將使用新的哈希表來存儲元素,并釋放舊哈希表的內(nèi)存空間。

責(zé)任編輯:武曉燕 來源: mikechen的互聯(lián)網(wǎng)架構(gòu)
相關(guān)推薦

2021-09-27 07:11:18

MySQLACID特性

2024-04-28 08:52:33

RabbitMQ延遲隊列延遲插件

2010-08-12 10:10:37

FlexMapABC

2010-04-20 20:53:35

實現(xiàn)網(wǎng)絡(luò)負(fù)載均衡

2010-02-26 16:16:15

2009-11-10 12:55:26

VB.NET三維模型

2021-03-02 07:02:45

Linux操作系統(tǒng)

2024-07-05 17:47:21

@Async項目啟動類

2009-11-16 13:04:04

PHP上傳文件代碼

2010-05-17 09:49:19

DataCore虛擬化

2018-05-10 22:40:44

物聯(lián)網(wǎng)信息化傳統(tǒng)企業(yè)

2023-02-17 14:35:15

HashMapNode類型

2024-06-28 07:42:18

2013-11-01 10:09:14

Windows 8.1升級

2021-05-06 11:06:52

人工智能語音識別聲聞檢索

2012-08-08 17:05:36

App運(yùn)營

2011-07-13 09:54:22

VMware故障vSphere

2009-02-04 09:45:05

Java SocketSocket APIJava編程

2020-11-04 00:00:29

Kerberos協(xié)議身份

2010-05-20 11:12:13

SVN插件安裝
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號