欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于hashmap 的擴(kuò)容和樹(shù)形化全面分析

 更新時(shí)間:2021年06月10日 17:16:34   作者:溫柔的謝世杰  
這篇文章主要介紹了hashmap 的擴(kuò)容和樹(shù)形化的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

一、樹(shù)形化

//鏈表轉(zhuǎn)紅黑樹(shù)的閾值
static final int TREEIFY_THRESHOLD = 8;
//紅黑樹(shù)轉(zhuǎn)鏈表的閾值
static final int UNTREEIFY_THRESHOLD = 6;
/**
*最小樹(shù)形化容量閾值:即 當(dāng)哈希表中的容量 > 該值時(shí),才允許樹(shù)形化鏈表 (即 將鏈表 轉(zhuǎn)換成紅黑樹(shù))
*否則,若桶內(nèi)元素太多時(shí),則直接擴(kuò)容,而不是樹(shù)形化
*為了避免進(jìn)行擴(kuò)容、樹(shù)形化選擇的沖突,這個(gè)值不能小于 4 * TREEIFY_THRESHOLD
**/
static final int MIN_TREEIFY_CAPACITY = 64;

第一個(gè)和第二個(gè)變量沒(méi)有什么問(wèn)題,關(guān)鍵是第三個(gè):是表示只有在數(shù)組長(zhǎng)度大于64的時(shí)候,才能樹(shù)形化列表嗎?

實(shí)際上,這兩個(gè)變量是應(yīng)用于不同場(chǎng)景的。

鏈表長(zhǎng)度大于8的時(shí)候就會(huì)調(diào)用treeifyBin方法轉(zhuǎn)化為紅黑樹(shù),但是在treeifyBin方法內(nèi)部卻有一個(gè)判斷,當(dāng)只有數(shù)組長(zhǎng)度大于64的時(shí)候,才會(huì)進(jìn)行樹(shù)形化,否則就只是resize擴(kuò)容。

為什么呢?

因?yàn)殒湵磉^(guò)長(zhǎng)而數(shù)組過(guò)短,會(huì)經(jīng)常發(fā)生hash碰撞,這個(gè)時(shí)候樹(shù)形化其實(shí)是治標(biāo)不治本,因?yàn)橐疰湵磉^(guò)長(zhǎng)的根本原因是數(shù)組過(guò)短。執(zhí)行樹(shù)形化之前,會(huì)先檢查數(shù)組長(zhǎng)度,如果長(zhǎng)度小于 64,則對(duì)數(shù)組進(jìn)行擴(kuò)容,而不是進(jìn)行樹(shù)形化。

所以發(fā)生擴(kuò)容的時(shí)候是在兩種情況下

超過(guò)閾值

鏈表長(zhǎng)度超過(guò)8,但是數(shù)值長(zhǎng)度不足64

二、擴(kuò)容機(jī)制

hashmap內(nèi)部創(chuàng)建過(guò)程

構(gòu)造器(只是初始化一下參數(shù),也就代表著只有添加數(shù)據(jù)的時(shí)候才會(huì)構(gòu)建數(shù)組和鏈表)—調(diào)用put方法—put方法會(huì)調(diào)用resize方法(在數(shù)組為空或者超過(guò)閾值的時(shí)候,put方法調(diào)用resize方法)

hashmap是如何擴(kuò)容的

1.hashmap中閾值threshold的設(shè)定

剛開(kāi)始,閾值設(shè)定為空

當(dāng)未聲明的hashmap的大小的時(shí)候,閾值設(shè)定就是默認(rèn)大小16*默認(rèn)負(fù)載因子0.75=12

當(dāng)聲明hashmap的大小的時(shí)候,會(huì)先調(diào)用一個(gè)函數(shù)把閾值設(shè)定為剛剛大于設(shè)定值的2的次方(比如說(shuō)設(shè)定的大小是1000,那閾值就是1024),然后在resize方法中,先把閾值賦給容量大小,然后在把容量大小*0.75在賦值給閾值。

代碼如下:

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
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;

2.數(shù)據(jù)轉(zhuǎn)移

當(dāng)數(shù)組為null的時(shí)候,會(huì)創(chuàng)建新的數(shù)組

當(dāng)數(shù)組不為空,會(huì)把容量和閾值均*2,并創(chuàng)建一個(gè)容量為之前二倍的數(shù)組,然后把原有數(shù)組的數(shù)據(jù)都轉(zhuǎn)移到新數(shù)組。

假設(shè)擴(kuò)容前的 table 大小為 2 的 N 次方,元素的 table 索引為其 hash 值的后 N 位確定

擴(kuò)容后的 table 大小即為 2 的 N+1 次方,則其中元素的 table 索引為其 hash 值的后 N+1 位確定,比原來(lái)多了一位

轉(zhuǎn)移數(shù)據(jù)不在跟1.7一樣重新計(jì)算hash值(計(jì)算hash值耗時(shí)巨大),只需要看索引中新增的是bit位是1還是0,

若為0則在新數(shù)組中與原來(lái)位置一樣,

若為1則在新 原位置+oldCap 即可。

三、容量計(jì)算公式

擴(kuò)容是一個(gè)特別耗性能的操作,所以當(dāng)程序員在使用 HashMap 的時(shí)候,估算 map 的大小,初始化的時(shí)候給一個(gè)大致的數(shù)值,避免 map 進(jìn)行頻繁的擴(kuò)容。

HashMap 的容量計(jì)算公式 :size/0.75 +1 。

原理就是保證,閾值(數(shù)組長(zhǎng)度*0.75)>實(shí)際容量

HashMap的最大容量為什么是2的30次方(1左移30)?

在閱讀hashmap的源碼過(guò)程中,我看到了關(guān)于hashmap最大容量的限制,并產(chǎn)生了一絲疑問(wèn)。

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

為啥最大容量是 1 << 30?

探究過(guò)程1 – 為什么是30

首先是 << 這個(gè)操作符必須要理解,在一般情況下 1 << x 等于 2^x。這是左移操作符,對(duì)二進(jìn)制進(jìn)行左移。

來(lái)看1 << 30。它代表將1左移30位,也就是0010...0

來(lái)看這樣一段代碼:

public static void main(String[] args){
        for (int i = 30; i <= 33; i++) {
            System.out.println("1 << "+ i +" = "+(1 << i));
        }
        System.out.println("1 << -1 = " + (1 << -1));
}

輸出結(jié)果為:

1 << 30 = 1073741824
1 << 31 = -2147483648
1 << 32 = 1
1 << 33 = 2
1 << -1 = -2147483648

結(jié)果分析:

  • int類型是32位整型,占4個(gè)字節(jié)。
  • Java的原始類型里沒(méi)有無(wú)符號(hào)類型。 -->所以首位是符號(hào)位 正數(shù)為0,負(fù)數(shù)為1
  • java中存放的是補(bǔ)碼,1左移31位的為 16進(jìn)制的0x80000000代表的是-2147483648–>所以最大只能是30

探究過(guò)程2 – 為什么是 1 << 30

探究完1相信大家對(duì) 為什么是30有一點(diǎn)點(diǎn)了解。那為什么是 1 << 30,而不是0x7fffffff即Integer.MAX_VALUE

我們首先看代碼的注釋

 /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

翻譯一下大概就是:如果構(gòu)造函數(shù)傳入的值大于該數(shù) ,那么替換成該數(shù)。

ok,我們看看構(gòu)造函數(shù)的調(diào)用:

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

其中這一句:

if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;

看到這有很有疑問(wèn)了,如果我要存的數(shù)目大于 MAXIMUM_CAPACITY,你還把我的容量縮小成 MAXIMUM_CAPACITY???

別急繼續(xù)看:在resize()方法中有一句:

if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
}

在這里我們可以看到其實(shí) hashmap的“最大容量“是Integer.MAX_VALUE;

總結(jié)

MAXIMUM_CAPACITY作為一個(gè)2的冪方中最大值,這個(gè)值的作用涉及的比較廣。其中有一點(diǎn)比較重要的是在hashmap中容量會(huì)確保是 2的k次方,即使你傳入的初始容量不是 2的k次方,tableSizeFor()方法也會(huì)將你的容量置為 2的k次方。這時(shí)候MAX_VALUE就代表了最大的容量值。

另外還有一點(diǎn)就是threshold,如果對(duì)hashmap有一點(diǎn)了解的人都會(huì)知道threshold = 初始容量 * 加載因子。也就是擴(kuò)容的 門檻。相當(dāng)于實(shí)際使用的容量。而擴(kuò)容都是翻倍的擴(kuò)容。那么當(dāng)容量到達(dá)MAXIMUM_CAPACITY,這時(shí)候再擴(kuò)容就是 1 << 31 整型溢出。

所以Integer.MAX_VALUE作為最終的容量,但是是一個(gè)threshold的身份。以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • MyBatis 如何獲取子類的屬性

    MyBatis 如何獲取子類的屬性

    這篇文章主要介紹了MyBatis 如何獲取子類的屬性,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java實(shí)現(xiàn)樹(shù)形結(jié)構(gòu)的示例代碼

    Java實(shí)現(xiàn)樹(shù)形結(jié)構(gòu)的示例代碼

    由于業(yè)務(wù)需要,后端需要返回一個(gè)樹(shù)型結(jié)構(gòu)給前端,包含父子節(jié)點(diǎn)的數(shù)據(jù)已經(jīng)在數(shù)據(jù)庫(kù)中存儲(chǔ)好。本文將為大家分享Java現(xiàn)樹(shù)形結(jié)構(gòu)的示例代碼,需要的可以參考下
    2022-05-05
  • Java文檔注釋超詳細(xì)講解

    Java文檔注釋超詳細(xì)講解

    這篇文章主要給大家介紹了關(guān)于Java文檔注釋的相關(guān)資料,文檔注釋主要是用來(lái)生成java開(kāi)發(fā)文檔javadoc的,生成的開(kāi)發(fā)文檔和Java本身的API幫助文檔是一樣的,需要的朋友可以參考下
    2023-10-10
  • SpringBoot3實(shí)現(xiàn)優(yōu)雅停機(jī)的完整流程

    SpringBoot3實(shí)現(xiàn)優(yōu)雅停機(jī)的完整流程

    在現(xiàn)代微服務(wù)架構(gòu)中,優(yōu)雅停機(jī)(Graceful Shutdown)是一項(xiàng)重要功能,可以確保服務(wù)在關(guān)閉時(shí)處理完所有當(dāng)前請(qǐng)求,避免突然終止連接或丟失數(shù)據(jù),Spring Boot 3 提供了對(duì)優(yōu)雅停機(jī)的內(nèi)置支持,本文給大家介紹了SpringBoot3怎樣優(yōu)雅停機(jī),需要的朋友可以參考下
    2024-10-10
  • Java使用IO流實(shí)現(xiàn)音頻的剪切和拼接

    Java使用IO流實(shí)現(xiàn)音頻的剪切和拼接

    這篇文章主要為大家詳細(xì)介紹了Java使用IO流實(shí)現(xiàn)音頻的剪切和拼接,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • 走進(jìn)JDK之不可變類String

    走進(jìn)JDK之不可變類String

    這篇文章主要給大家介紹了JDK之不可變類String的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用JDK具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 一文帶你了解Java選擇排序的原理與實(shí)現(xiàn)

    一文帶你了解Java選擇排序的原理與實(shí)現(xiàn)

    選擇排序:(Selection sort)是一種簡(jiǎn)單直觀的排序算法,也是一種不穩(wěn)定的排序方法。本文主要為大家介紹一下選擇排序的原理與實(shí)現(xiàn),希望對(duì)大家有所幫助
    2022-11-11
  • Java設(shè)計(jì)模式七大原則之單一職責(zé)原則詳解

    Java設(shè)計(jì)模式七大原則之單一職責(zé)原則詳解

    單一職責(zé)原則(Single Responsibility Principle, SRP),有且僅有一個(gè)原因引起類的變更。簡(jiǎn)單來(lái)說(shuō),就是針對(duì)一個(gè)java類,它應(yīng)該只負(fù)責(zé)一項(xiàng)職責(zé)。本文將詳細(xì)介紹一下Java設(shè)計(jì)模式七大原則之一的單一職責(zé)原則,需要的可以參考一下
    2022-02-02
  • mybatis如何實(shí)現(xiàn)繼承映射

    mybatis如何實(shí)現(xiàn)繼承映射

    這篇文章主要介紹了mybatis如何實(shí)現(xiàn)繼承映射的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java?NIO實(shí)現(xiàn)聊天室功能

    Java?NIO實(shí)現(xiàn)聊天室功能

    這篇文章主要為大家詳細(xì)介紹了Java?NIO實(shí)現(xiàn)聊天室功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11

最新評(píng)論