精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

徹底搞懂hashMap底層原理

開發(fā)
HashMap的數(shù)據(jù)結(jié)構(gòu)是由數(shù)組和鏈表組成,table是一個(gè)存放Entry對(duì)象的數(shù)組,每個(gè)Entry對(duì)象由4個(gè)屬性組成。

一、說明

hashMap在java1.7和java1.8版本中有做一些調(diào)整,我們本篇只說java1.7的hashMap。

二、數(shù)據(jù)結(jié)構(gòu)

hashMap的數(shù)據(jù)結(jié)構(gòu)是由數(shù)組和鏈表組成,table是一個(gè)存放Entry對(duì)象的數(shù)組,每個(gè)Entry對(duì)象由4個(gè)屬性組成,分別是key、value、next、hash,key和value是我們熟知的鍵值對(duì),不需要過多解釋,next是當(dāng)前元素在鏈表中指向下一個(gè)元素的引用,hash是計(jì)算出來(lái)的hashcode,hashMap中的hsah是通過對(duì)key.hashcode()進(jìn)行一定操作得出的,并不是直接使用key.hashcode()方法計(jì)算數(shù)來(lái)的值。

三、屬性信息

先來(lái)了解下hashMap中一些重要的屬性:

//Hashmap的初始化大小,初始化的值為16,1往右移4位為16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 

//HashMap是動(dòng)態(tài)擴(kuò)容的,就是容量大小不能大于 1<<30
static final int MAXIMUM_CAPACITY = 1 << 30;

//默認(rèn)的擴(kuò)容因子,這個(gè)值可以通過構(gòu)造修改
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//空數(shù)據(jù),默認(rèn)構(gòu)造的時(shí)候賦值為空的Entry數(shù)組,在添加元素的時(shí)候
//會(huì)判斷table=EMPTY_TABLE ,然后就擴(kuò)容
static final Entry<?,?>[] EMPTY_TABLE = {};

//表示一個(gè)空的Hashmap
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

//Hashmap的大小
transient int size;

//threshold表示當(dāng)HashMap的size大于threshold時(shí)會(huì)執(zhí)行resize操作。
DEFAULT_INITIAL_CAPACITY=16

//擴(kuò)容的閾值
int threshold;

//擴(kuò)容因子,沒有傳入就使用默認(rèn)的DEFAULT_LOAD_FACTOR = 0.75f
final float loadFactor;

//數(shù)據(jù)操作次數(shù),用于迭代檢查修改異常
transient int modCount;

static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

四、put方法

步驟:

  1. 判斷table是否為空,如果為空則初始化,不為空則下一步
  2. 判斷key是否為null,如果為null則處理,不為null則下一步
  3. 根據(jù)key計(jì)算下標(biāo)
  4. 如果下標(biāo)處的桶不為空,則從下標(biāo)處開始遍歷鏈表,如果找到key相同的則更新覆蓋并返回舊值,如果為空則下一步
  5. 插入前判斷是否需要擴(kuò)容,如果需要?jiǎng)t進(jìn)行擴(kuò)容,如果不需要就下一步
  6. 頭插法插入桶中

接下來(lái)我們對(duì)每一步驟分別展開討論。

1.table初始化

private void inflateTable(int toSize) {
    // 找到大于等于指定數(shù)組長(zhǎng)度的2的n次方
    int capacity = roundUpToPowerOf2(toSize);
 // 擴(kuò)容閾值,數(shù)組長(zhǎng)度*負(fù)載因子
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    // 創(chuàng)建數(shù)組
    table = new Entry[capacity];
    // 是否重新賦值hashSeed 
    initHashSeedAsNeeded(capacity);
}

private static int roundUpToPowerOf2(int number) {
    // 使用Integer的highestOneBit方法找到大于等于指定數(shù)組長(zhǎng)度的2的n次方
    return number >= MAXIMUM_CAPACITY
            ? MAXIMUM_CAPACITY
            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}

數(shù)組初始化其實(shí)就三個(gè)步驟:計(jì)算數(shù)組容量,創(chuàng)建數(shù)組,判斷是否重hash。

(1)「數(shù)組容量」: 如果指定數(shù)組長(zhǎng)度值大于MAXIMUM_CAPACITY(最大數(shù)組容量:2的30次方),就用最大值;如果指定數(shù)組長(zhǎng)度值小于等于1,就用1;如果指定數(shù)組長(zhǎng)度值大于1,就用下面的方法得到大于等于指定數(shù)組長(zhǎng)度的第一個(gè)2的n次方的值。

Integer.highestOneBit((number - 1) << 1)

public static int highestOneBit(int i) {
        // HD, Figure 3-1
        i |= (i >>  1);
        i |= (i >>  2);
        i |= (i >>  4);
        i |= (i >>  8);
        i |= (i >> 16);
        return i - (i >>> 1);
    }

這個(gè)方法內(nèi)部是通過位移和亦或操作得到的值,感興趣的可以直接看下源碼。

(2) 「創(chuàng)建數(shù)組」:直接用計(jì)算出來(lái)的數(shù)組長(zhǎng)度創(chuàng)建Entry數(shù)組table,元素類型為Entry。

(3) 「判斷是否重hash」:

重hash就是對(duì)同個(gè)key重新計(jì)算hash值,那么為什么要重新計(jì)算hash值,實(shí)際上只是為了讓hsah值更復(fù)雜,在計(jì)算下標(biāo)的時(shí)候就會(huì)更加散列,減少hash沖突。

那么什么樣的條件下才會(huì)重hash呢,從源碼可以看出switching為true表示需要重hash,影響switching取值的是下面這段代碼:

final boolean initHashSeedAsNeeded(int capacity) {
 // hashSeed 初始值為0,false
    boolean currentAltHashing = hashSeed != 0;
    // 數(shù)組長(zhǎng)度是否 >= 2^31-1 
    boolean useAltHashing = sun.misc.VM.isBooted() &&
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    // 使用異或,currentAltHashing 為false,只有數(shù)組長(zhǎng)度>= 2^31-1時(shí)返回true
    boolean switching = currentAltHashing ^ useAltHashing;
    //switching為true,則hashSeed重新賦值(一般不會(huì)出現(xiàn))
    if (switching) {
        hashSeed = useAltHashing
            ? sun.misc.Hashing.randomHashSeed(this)
            : 0;
    }
    return switching;
}

initHashSeedAsNeeded方法用來(lái)判斷是否進(jìn)行重hash,如果需要重hash,會(huì)同步更新hash種子,最后返回boolean類型的值。

  • sun.misc.VM.isBooted()指jvm運(yùn)行狀態(tài),一般為true;
  • hashSeed初始為0,所以currentAltHashing一定為false;

Holder.ALTERNATIVE_HASHING_THRESHOLD取的是環(huán)境變量jdk.map.althashing.threshold配置的值(程序員配置),如果沒有配置就默認(rèn)取Integer.MAX_VALUE。

通過上面的分析可以知道是否進(jìn)行重hash只會(huì)受到capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD的影響。

「我們可以得出一個(gè)結(jié)論:」如果程序員不配置環(huán)境變量jdk.map.althashing.threshold,那么就永遠(yuǎn)不會(huì)進(jìn)行重hash,因?yàn)閿?shù)組長(zhǎng)度capacity最大是2的30次方,而Integer.MAX_VALUE的值是2的31次方減1,即這個(gè)條件永遠(yuǎn)不會(huì)滿足,但是你可能會(huì)說擴(kuò)容的時(shí)候傳入的capacity正好是最大值2的30次方的2倍,但是我會(huì)告訴你,如果數(shù)組成都達(dá)到2的30次方,是不允許擴(kuò)容的。

所以說如果程序員不設(shè)置環(huán)境變量,initHashSeedAsNeeded方法實(shí)際上沒有意義的。

那為什么要更新hash種子呢?

這就要了解下hsah值是怎么生成的了:

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }
 
    h ^= k.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

hashMap的hsah值是由key調(diào)用自身的hashCode方法得到的值與hashSeed進(jìn)行5次亦或操作4次位移運(yùn)算得到的。

所以相同的key只有在hashSeed變化后才有生成不同的hash值,否則生成的永遠(yuǎn)是相同的hsah值,所以需要重hash的時(shí)候就必須改變hashSeed,否則重hash的結(jié)果會(huì)和原來(lái)一樣。

2.處理key為null

private V putForNullKey(V value) {
 // 當(dāng)key為null時(shí),數(shù)組下標(biāo)指定為0
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
     // 判斷現(xiàn)在有沒有key為null的Entry
        if (e.key == null) {
         //value替換為新值,返回舊的value
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    //修改次數(shù)+1
    modCount++;
    // table[0]中沒有值或沒有匹配到null的key,創(chuàng)建一個(gè)Entry放入table[0]
    addEntry(0, null, value, 0);
    return null;
}

上面這段代碼是對(duì)key為null的情況的處理,同時(shí)也說明hashmap是允許key為null的,通過上面可以看出hashMap中key為null的元素只會(huì)存儲(chǔ)在數(shù)組下標(biāo)為0的桶中,如果桶中有多個(gè),就遍歷鏈表找到key為null的元素進(jìn)行覆蓋更新,如果桶中無(wú)元素,就調(diào)用addEntry方法插入元素,這里只需要知道調(diào)用addEntry方法的結(jié)果是將數(shù)據(jù)插入數(shù)組下標(biāo)為0的桶中,addEntry方法我們下面再具體看。

3.計(jì)算下標(biāo)

// 獲取key的hash值
    int hash = hash(key);
    // 根據(jù)hash值和數(shù)組長(zhǎng)度計(jì)算要放入的數(shù)組下標(biāo)位置
    int i = indexFor(hash, table.length);

計(jì)算下標(biāo)實(shí)際上分為兩步,計(jì)算hsah值和計(jì)算下標(biāo),計(jì)算下標(biāo)的原理是用hash值除以數(shù)組容量,得到的余數(shù)就是下標(biāo),用這種方式可以保證不同的key一定會(huì)放在數(shù)組中的某個(gè)桶中,不會(huì)越界,而hash值可以讓不同的key在數(shù)組中的分部足夠分散,減少hsah沖突。

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }
 
    h ^= k.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

上面已將說過hash方法,這里再來(lái)說一次,我們知道java中每個(gè)類都默認(rèn)由hashcode方法生成hashcode,但是hashMap中并沒有直接用此方法生成的hashcode,而是對(duì)其生成的hahshcode與hash種子進(jìn)行亦或和位移操作,1.7的hashMap在計(jì)算hsah的時(shí)候進(jìn)行了5次亦或操作和4次位移運(yùn)算,這樣做的目的就是使得不同的key計(jì)算出來(lái)的hash更加分散,更加能減少hash沖突。

static int indexFor(int h, int length) {
    // 計(jì)算數(shù)組下標(biāo)位置,數(shù)組長(zhǎng)度必須為2的n次方
    return h & (length-1);
}

我們上面說了hash除以數(shù)組容量得到數(shù)組下標(biāo),但是這種做法在java中太慢了,而和此做法同效的一種方式就是h & (length-1),就是數(shù)組容量減去1,再與hash做&操作。這種方式在java中是非常高效的。

4.遍歷查找key

運(yùn)行到這里,數(shù)組已經(jīng)有了,key對(duì)應(yīng)的下標(biāo)也有了,接下來(lái)就是插入操作,插入之前會(huì)先查看下標(biāo)對(duì)應(yīng)的桶中是否為空桶,如果不為空桶就要先遍歷查找是否有相同key。

for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        // 判斷key的值是否相等
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
         // key的值相等則寫入新的value值,將舊value返回
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    
   // 修改次數(shù)+1
    modCount++;
    // 下標(biāo)位置為空或沒有能夠匹配的key值,創(chuàng)建Entry放入鏈表
    addEntry(hash, key, value, i);

因?yàn)閔ashMap是由數(shù)組和鏈表組成,數(shù)組的每個(gè)桶中都由鏈表組成,所以需要遍歷鏈表查找相同的key,如果存在相同的key就更新覆蓋,如果沒有,就調(diào)用addEntry方法進(jìn)行插入。

//addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
 // 如果當(dāng)前數(shù)組長(zhǎng)度>=擴(kuò)容閾值 并且 當(dāng)前數(shù)組下標(biāo)位置不為null
    if ((size >= threshold) && (null != table[bucketIndex])) {
     // 數(shù)組擴(kuò)容為當(dāng)前長(zhǎng)度*2
        resize(2 * table.length);
        // key是否為null,不為null計(jì)算hash值,null則直接hash值為0
        hash = (null != key) ? hash(key) : 0;
        // 根據(jù)hash值和新數(shù)組長(zhǎng)度計(jì)算下標(biāo)位置
        bucketIndex = indexFor(hash, table.length);
    }
 //創(chuàng)建Entry放入table中
    createEntry(hash, key, value, bucketIndex);
}

可以看到在正式添加元素之前會(huì)先判斷是否需要擴(kuò)容,如果需要?jiǎng)t先進(jìn)行擴(kuò)容。

5.擴(kuò)容處理

從上面的源碼可以知道擴(kuò)容需要滿足兩個(gè)條件:

  • 數(shù)組長(zhǎng)度達(dá)到threshold,threshold是由負(fù)載因子和數(shù)組容量計(jì)算出來(lái)的
  • 當(dāng)前數(shù)組下標(biāo)對(duì)應(yīng)的桶不為null

如果滿足條件則進(jìn)入resize方法進(jìn)行擴(kuò)容:

void resize(int newCapacity) {
 // 原數(shù)組
    Entry[] oldTable = table;
    // 原數(shù)組長(zhǎng)度
    int oldCapacity = oldTable.length;
    // 如果原數(shù)組長(zhǎng)度為2^30,不進(jìn)行擴(kuò)容,把擴(kuò)容閾值修改為2^31-1
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
 // 根據(jù)新的數(shù)組長(zhǎng)度,創(chuàng)建數(shù)組
    Entry[] newTable = new Entry[newCapacity];
    // 轉(zhuǎn)移數(shù)組數(shù)據(jù) 
    // 調(diào)用initHashSeedAsNeeded方法,根據(jù)新數(shù)組的長(zhǎng)度判斷是否會(huì)hashSeed重新賦值
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    // table指向新數(shù)組
    table = newTable;
    // 計(jì)算新的擴(kuò)容閾值
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

// transfer方法
void transfer(Entry[] newTable, boolean rehash) {
 // 新數(shù)組長(zhǎng)度
    int newCapacity = newTable.length;
    // 遍歷原數(shù)組的Entry
    for (Entry<K,V> e : table) {
     // Entry不為null
        while(null != e) {
         // 先找到鏈表中下一個(gè)要轉(zhuǎn)移的Entry
            Entry<K,V> next = e.next;
            // 如果hashSeed變了,重新進(jìn)行hash計(jì)算
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            // 重新計(jì)算數(shù)組下標(biāo),結(jié)果分兩種:1.與原下標(biāo)一直 2.原下標(biāo)+本次擴(kuò)容了多長(zhǎng)
            int i = indexFor(e.hash, newCapacity);
            /*原數(shù)組統(tǒng)一鏈表中的數(shù)據(jù)轉(zhuǎn)移到新數(shù)組中時(shí),所處鏈表順序顛倒,因此多線程的情況下可能會(huì)出現(xiàn)環(huán)形鏈表問題*/
            // Entry的next指向新數(shù)組的鏈表頭
            e.next = newTable[i];
            // Entry放入新數(shù)組的鏈表中
            newTable[i] = e;
            // 下一次進(jìn)行操作的就是原數(shù)組鏈表的下一個(gè)
            e = next;
        }
    }
}

擴(kuò)容步驟:

  • 首先傳入的newCapacity是數(shù)組容量的2倍,也是2的n次方
  • 如果數(shù)組的容量已經(jīng)達(dá)到2的30次方,則不進(jìn)行擴(kuò)容,直接返回
  • 用新的數(shù)組長(zhǎng)度newCapacity創(chuàng)建Entry數(shù)組
  • initHashSeedAsNeeded判斷是否要重hash,并更新hash種子
  • transfer方法進(jìn)行擴(kuò)容處理

transfer方法進(jìn)行具體的擴(kuò)容處理:

實(shí)際上就是遍歷舊數(shù)組,從舊數(shù)組的第一個(gè)桶中拿到鏈表,從鏈表頭部開始遍歷,一個(gè)一個(gè)的取出計(jì)算新的下標(biāo)(如果需要重hash就會(huì)用新的hsah種子計(jì)算hsah值,如果不需要重hash就用原來(lái)的hsah值,最終用hsah值和新數(shù)組容量計(jì)算下標(biāo)),然后頭插法插到新的數(shù)組中。

你會(huì)發(fā)現(xiàn)兩個(gè)規(guī)律:

  • 原來(lái)數(shù)組中的鏈表和新數(shù)組中鏈表的順序相反。
  • 計(jì)算所得的新下標(biāo)要么等于原下標(biāo)值,要么等于原下標(biāo)加擴(kuò)容的長(zhǎng)度。

我們通過一個(gè)例子來(lái)分析一下第二條規(guī)律。

假設(shè)table.length=16,現(xiàn)在有兩個(gè)key,key1對(duì)應(yīng)的hash值為68,key2對(duì)應(yīng)hash值為84,根據(jù)公式h & (length-1)計(jì)算,&運(yùn)算規(guī)則是遇0為0,結(jié)果如下:

68 key1 0100 0100 & 0000 1111  =0000 0100  =4 84 key2 0101 0100 & 0000 1111  =0000 0100  =4

可見兩個(gè)值都落在table[4]這個(gè)桶中。

擴(kuò)容一次后table.length=32,再根據(jù)公式h & (length-1)計(jì)算結(jié)果如下:

68 key1 0100 0100 & 0001 1111  =0000 0100  =4 84 key2 0101 0100 & 0001 1111  =0001 0100  =20

可見68依然被放在新數(shù)組的table[4],而84被放在table[20]。

再擴(kuò)容一次后table.length=64,再根據(jù)公式h & (length-1)計(jì)算結(jié)果如下:

68 key1 0100 0100 & 0011 1111  =0000 0100  =4 84 key2 0101 0100 & 0011 1111  =0001 0100  =20

可見兩個(gè)值還在原來(lái)的數(shù)組下標(biāo)對(duì)應(yīng)的桶中。

結(jié)論:同一個(gè)桶中的鏈表中的數(shù)據(jù),擴(kuò)容后,在新數(shù)組中的下標(biāo)要么和原數(shù)組相同,要么是原數(shù)組下標(biāo)加擴(kuò)容的長(zhǎng)度。

這個(gè)規(guī)律是怎么形成的呢?

這得益于數(shù)組的容量都為2的n次方,數(shù)組每次擴(kuò)容的后結(jié)果都是原來(lái)數(shù)組容量的兩倍,例如:16,32,64...,length-1的結(jié)果分別是15,31,63,而對(duì)應(yīng)的二進(jìn)制如下:

0000 1111
0001 1111
0011 1111

可以看的出,每次擴(kuò)容都是高位加1,就導(dǎo)致在計(jì)算的時(shí)候只需要看hash值中與高位對(duì)應(yīng)的那個(gè)位上是0還是1,也就導(dǎo)致新數(shù)組中的下標(biāo)只有兩種可能:如果是0下標(biāo)不變,如果是1下標(biāo)就會(huì)變化。

這個(gè)規(guī)律有什么好處呢?

這個(gè)規(guī)律可以使原來(lái)在同個(gè)桶中的數(shù)據(jù)可以分散到其他桶中,使得數(shù)組分部更加均勻,減少hash沖突,擴(kuò)容的時(shí)候同個(gè)桶中的數(shù)據(jù)將會(huì)被分部到新數(shù)組中的哪個(gè)桶中,可以通過只判斷高位就能確定,所以可以以此來(lái)優(yōu)化擴(kuò)容效率。

6.頭插法

// createEntry方法
void createEntry(int hash, K key, V value, int bucketIndex) {
 // 獲取當(dāng)前數(shù)組下標(biāo)位置的鏈表頭
    Entry<K,V> e = table[bucketIndex];
    // 在鏈表頭位置創(chuàng)建新的Entry,next指向原鏈表頭(頭插法)
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    // 數(shù)組長(zhǎng)度+1
    size++;
}

頭插法的源碼就很簡(jiǎn)單了,就是新建一個(gè)Entry對(duì)象,新建Entry對(duì)象的next屬性指向當(dāng)前坐標(biāo)下的頭部Entry對(duì)象,再把新的Entry對(duì)象引用賦值給當(dāng)前數(shù)組下標(biāo)處。

這里的文字說明可能比自己看源碼更繞。自己看源碼應(yīng)該一目了然。

五、問題匯總

1.為什么要計(jì)算得到大于等于指定數(shù)組長(zhǎng)度的第一個(gè)2的n次方的值?

只有在數(shù)組長(zhǎng)度為2的n次方時(shí),數(shù)組長(zhǎng)度-1轉(zhuǎn)換為2進(jìn)制時(shí)才可以轉(zhuǎn)換為低位全部為1的二進(jìn)制,和hash值進(jìn)行&運(yùn)算時(shí)才能等效于hash值除以數(shù)組容量求余的結(jié)果。

2.為什么要用頭插法?

實(shí)際上無(wú)論是頭插法還是尾插法,都是需要遍歷鏈表的,如果在遍歷過程中找到key相同的,則更新覆蓋,這種情況不會(huì)有插入操作,所以無(wú)所謂頭插法和尾插法,但是如果沒有找到key相同的元素,那這時(shí)肯定已經(jīng)遍歷到鏈表的尾部了,所以但凡插入,不管是頭插法還是尾插法都不會(huì)在遍歷鏈表上節(jié)省時(shí)間,又因?yàn)殒湵淼牟迦雰H僅是更換next屬性的指針,所以兩種插入方式的效率是沒有區(qū)別的。

java1.7之所以使用頭插法應(yīng)該和自身的代碼結(jié)構(gòu)有關(guān),因?yàn)椴迦敕椒ㄊ仟?dú)立的,如果用尾插法,就要在遍歷的時(shí)候記錄最后一個(gè)元素的值,而頭插法就不需要了,但是我覺得這個(gè)不是主要原因,個(gè)人覺得java開發(fā)者只不過是兩者選擇了一個(gè)而已,沒有什么特殊考慮,否則也不至于會(huì)有循環(huán)鏈表的問題了。

3.hashMap為什么是線程不安全的?

hashMap線程不安全主要表現(xiàn)在兩個(gè)方面:

(1)多線程插入數(shù)據(jù)的時(shí)候,數(shù)據(jù)丟失問題

void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

上面是頭插法的代碼邏輯,在多線程操作下,如果兩個(gè)線程同時(shí)走到方法內(nèi)第一行,那么獲取到的e是相同的,然后兩個(gè)線程分別創(chuàng)建Entry對(duì)象,并且Entry對(duì)象的next屬性賦值為e,這樣一來(lái)總會(huì)有一個(gè)線程的數(shù)據(jù)被丟掉了。

(2) 多線程情況下擴(kuò)容的時(shí)候可能會(huì)發(fā)生循環(huán)鏈表

循環(huán)鏈表發(fā)生在多線程擴(kuò)容的情況下,下面是擴(kuò)容的部分代碼:

for (Entry<K,V> e : table) {
    while(null != e) {
        Entry<K,V> next = e.next;
        if (rehash) {
          e.hash = null == e.key ? 0 : hash(e.key);
        }
        int i = indexFor(e.hash, newCapacity);
        e.next = newTable[i];
        newTable[i] = e;
        e = next;
    }

這段代碼邏輯是把舊數(shù)組中的數(shù)據(jù)從鏈表頭部一個(gè)個(gè)利用頭插法插入到新數(shù)組中。

假設(shè)有兩個(gè)線程同時(shí)進(jìn)行擴(kuò)容,兩條線程都執(zhí)行到下面這行代碼:

Entry<K,V> next = e.next;

此時(shí)第一條線程繼續(xù)執(zhí)行,第二條線程卡住,等到第一條線程整個(gè)循環(huán)執(zhí)行結(jié)束后,第二條線程才繼續(xù)執(zhí)行。

此時(shí)第一條線程擴(kuò)容完成,鏈表的指向和原數(shù)組的順序相反。假設(shè)原數(shù)組的某個(gè)桶中鏈表方向是1>2>3>4,擴(kuò)容后又恰好都落在同一個(gè)新的桶中,那么新的鏈表方向是4>3>2>1。

此時(shí)第二條線程開始執(zhí)行循環(huán):

  • 第一輪循環(huán)開始 e指的是1,next指的是2 頭插法插入新的數(shù)組,新數(shù)組的鏈表為1>null
  • 第二輪循環(huán)開始 e指的是2,next指的是1 頭插法插入新的數(shù)組,新數(shù)組的鏈表為2>1>null
  • 第三輪循環(huán)開始 e指的是1,next指的是null 頭插法插入新的數(shù)組,新數(shù)組的鏈表為1>2>1

此時(shí)循環(huán)鏈表出現(xiàn)了。

責(zé)任編輯:趙寧寧 來(lái)源: 碼農(nóng)本農(nóng)
相關(guān)推薦

2023-01-04 07:54:03

HashMap底層JDK

2021-10-11 11:58:41

Channel原理recvq

2021-10-09 19:05:06

channelGo原理

2023-05-29 08:12:38

2023-07-11 08:00:00

2021-07-08 10:08:03

DvaJS前端Dva

2022-04-24 11:06:54

SpringBootjar代碼

2022-08-26 13:24:03

version源碼sources

2021-07-16 11:35:20

Java線程池代碼

2025-11-13 08:08:15

2021-08-29 07:41:48

數(shù)據(jù)HashMap底層

2025-04-21 04:00:00

2020-04-28 22:12:30

Nginx正向代理反向代理

2021-09-22 07:58:51

Linux 系統(tǒng)調(diào)用Linux 系統(tǒng)

2025-06-30 00:32:43

策略模式算法MyBatis

2017-12-05 17:44:31

機(jī)器學(xué)習(xí)CNN卷積層

2025-05-06 01:14:00

系統(tǒng)編程響應(yīng)式

2020-10-14 08:50:38

搞懂 Netty 線程

2024-01-03 13:39:00

JS,Javascrip算法

2025-01-13 16:00:00

服務(wù)網(wǎng)關(guān)分布式系統(tǒng)架構(gòu)
點(diǎn)贊
收藏

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

视频一区在线| 国产美女性感在线观看懂色av| 亚洲精品一二三区区别| 日韩美女天天操| 97超碰青青草| 国产素人视频在线观看| 波多野结衣视频一区| 国产成人精品最新| 久久99久久久| 欧美一区二区三| 日韩欧美一区二区久久婷婷| aⅴ在线免费观看| 性欧美ⅴideo另类hd| 久久精品视频免费观看| 国产这里只有精品| 男女啊啊啊视频| 这里只有精品在线| 亚洲男人天堂网| 久久精品无码一区二区三区毛片| 周于希免费高清在线观看| 中文字幕一区二区三区视频| 精品麻豆av| 国产乱人乱偷精品视频a人人澡| 国产日韩高清一区二区三区在线| 精品国产美女在线| 免费观看av网站| 伊人久久大香线蕉av超碰| 在线亚洲人成电影网站色www| 轻点好疼好大好爽视频| jizz在线免费观看| 久久综合国产精品| 99re视频在线播放| 91精品国产乱码久久| 每日更新成人在线视频| 久久久噜噜噜久久| 婷婷久久综合网| 人人狠狠综合久久亚洲婷婷| 亚洲女人天堂av| 性色av蜜臀av浪潮av老女人| 另类视频一区二区三区| 欧美精品 国产精品| 国产熟人av一二三区| 欧美激情网站| 精品福利视频导航| 波多野结衣综合网| 草莓视频丝瓜在线观看丝瓜18| 中文字幕中文字幕中文字幕亚洲无线| 欧美亚洲另类久久综合| 天堂a√在线| 99视频有精品| 精品久久久久久中文字幕动漫| 亚洲不卡免费视频| 国产suv精品一区二区883| 96pao国产成视频永久免费| 在线视频播放大全| 免费看欧美美女黄的网站| 国产精品成人va在线观看| 超碰在线免费97| 日韩国产欧美视频| 国产精品久久久久秋霞鲁丝| 波多野结衣在线电影| 日本强好片久久久久久aaa| 国产成人久久精品| 中文字幕在线观看视频一区| 麻豆91精品91久久久的内涵| 91精品国产综合久久香蕉最新版| 中文字幕视频在线播放| 久久精品72免费观看| 国产在线一区二区三区| 国产精品视频一二区| 精品制服美女丁香| 91在线免费网站| 好男人在线视频www| av在线综合网| 日本不卡一区| 男人的天堂在线视频免费观看| 日韩一区中文字幕| 日韩精品免费一区| 伊人久久综合一区二区| 在线欧美一区二区| 亚洲综合20p| 高清日韩中文字幕| 亚洲欧洲xxxx| 911国产在线| 精品成人免费| 国产精品白嫩初高中害羞小美女| 国产一区二区在线播放视频| 粉嫩欧美一区二区三区高清影视| 精品国产免费久久久久久尖叫| 欧美69xxxxx| 国产精品国产a| av高清在线免费观看| 国产一区二区主播在线| 欧美一区二区二区| 久久中文字幕人妻| 在线精品小视频| 日本久久久久久久久| 一级视频在线播放| 播五月开心婷婷综合| 日韩av图片| 直接在线观看的三级网址| 精品久久久一区二区| 男女视频在线看| 一区三区自拍| 日日骚久久av| 久久艹免费视频| 麻豆精品一区二区综合av| 国产精品久久7| 三级外国片在线观看视频| 香蕉成人啪国产精品视频综合网| 天堂网在线免费观看| 国产女人18毛片水真多18精品| 在线看片第一页欧美| 免费毛片一区二区三区| 精品伊人久久久久7777人| 精品国产免费久久久久久尖叫| 黄色在线观看网站| 欧美日韩一区二区三区| 一本岛在线视频| 亚州国产精品| 海角国产乱辈乱精品视频| 亚洲无码久久久久久久| 久久婷婷色综合| 免费无码毛片一区二三区| 国产精品视频一区二区三区综合| 亚洲色图17p| 五月婷婷中文字幕| 国产成人av电影在线观看| 制服国产精品| 97精品国产99久久久久久免费| 日韩成人免费视频| 久久综合加勒比| 国产剧情一区在线| 中文网丁香综合网| 懂色aⅴ精品一区二区三区| 亚洲欧美精品一区二区| 色网站在线播放| 成人蜜臀av电影| 免费看日本黄色| 精品视频成人| 欧美久久久精品| av中文字幕播放| 成人欧美一区二区三区在线播放| 亚洲综合婷婷久久| 日韩av在线中文字幕| 国产精品偷伦视频免费观看国产| 国内av一区二区三区| 日本精品免费观看高清观看| 精品无人区无码乱码毛片国产| 国产精品毛片在线看| 国产丝袜不卡| 咪咪网在线视频| 日韩精品视频观看| 久久夜色精品国产噜噜亚洲av| 91麻豆免费视频| 国产一区二区三区精彩视频| 亚洲综合小说图片| 国产精品美女在线观看| 最新av网站在线观看| 欧美日韩小视频| 视频国产一区二区| 国产乱子轮精品视频| 国产一级不卡视频| 久久91在线| 亲子乱一区二区三区电影| 成人在线免费看| 欧美日韩国产综合一区二区三区| 国产天堂av在线| 国产成人亚洲综合色影视| 蜜臀av色欲a片无码精品一区| 美女一区2区| 国产精品成人品| 国产1区在线| 亚洲国产精品va| 国产一卡二卡三卡| 亚洲色图在线视频| 欧美在线一级片| 久热精品在线| 日本xxxxx18| 天天做夜夜做人人爱精品 | 午夜69成人做爰视频| 韩国女主播成人在线| 欧美日韩不卡在线视频| 国产精品美女久久久久久不卡 | 久久中文字幕视频| 乱精品一区字幕二区| 色综合激情五月| 中文字幕无码日韩专区免费| 不卡av在线免费观看| 日本中文字幕高清| 欧美精品黄色| 日本一区二区三区视频免费看| 图片一区二区| 欧美一二三视频| 欧美一区二区三区| 日韩av网址在线观看| 自拍偷拍精品视频| 亚洲福利一二三区| 美女福利视频网| 99久久精品国产毛片| 色免费在线视频| 亚洲精品女人| 欧洲金发美女大战黑人| 久久91精品| 99视频日韩| 精品美女一区| 欧美一级视频免费在线观看| 18网站在线观看| 一本色道久久88综合亚洲精品ⅰ | 91国产精品| 青青草精品毛片| 色呦呦在线视频| 日韩在线视频导航| 欧美成人综合在线| 亚洲国产精品中文| 国产黄色一区二区| 91成人网在线| 日韩手机在线观看| 一区二区在线观看视频| 萌白酱视频在线| 久久人人爽人人爽| 成人做爰www看视频软件| 精品亚洲国产成人av制服丝袜| 欧美色图另类小说| 亚洲第一毛片| 成人免费观看在线| 久久精品亚洲欧美日韩精品中文字幕| 免费日韩av电影| 精品成人自拍视频| 春色成人在线视频| www一区二区三区| 国产区精品在线观看| 欧洲av不卡| 日韩免费在线免费观看| 三级在线观看视频| 97婷婷涩涩精品一区| 波多野结衣在线播放| 欧美黄色成人网| 性欧美ⅴideo另类hd| 欧美福利视频在线观看| 少妇视频在线| 久久青草福利网站| av影院在线免费观看| 97欧美精品一区二区三区| 成人女同在线观看| 久久久久免费视频| caoporn-草棚在线视频最| 久久久久久亚洲精品不卡| 国产理论电影在线| 97香蕉超级碰碰久久免费的优势| 国内激情视频在线观看| 97久久精品在线| 欧美成人影院| 国产精品高清免费在线观看| 成人免费视频观看| 国产精品偷伦视频免费观看国产| 日韩黄色三级| 亚洲xxxx视频| 国产精品调教| 欧美精品欧美精品系列c| 国产精品亚洲二区| 亚洲综合首页| 欧美日韩1区2区3区| 成人在线播放网址| 国产精品入口| 亚洲xxxx2d动漫1| 国产一区二区三区不卡在线观看| 曰本三级日本三级日本三级| 成人av午夜电影| 久操视频免费看| 中文字幕在线观看不卡| 青青草原免费观看| 丁香五六月婷婷久久激情| 国产99免费视频| 538prom精品视频线放| 亚洲国产精品国自产拍久久| 精品视频偷偷看在线观看| 第三区美女视频在线| 精品中文字幕在线| 在线观看福利电影| 成人高清视频观看www| 久久精品福利| 午夜精品福利一区二区| 欧美精品首页| 成年人黄色片视频| 国产一区二区三区美女| 五月天激情小说| 国产精品三级av| 国产一级黄色av| 欧美在线制服丝袜| 亚洲av综合色区无码一区爱av| 亚洲天堂免费在线| 色女人在线视频| 日韩免费视频在线观看| 97品白浆高清久久久久久| 日本在线观看一区| 激情自拍一区| 三级一区二区三区| 久久一区二区三区四区| avtt天堂在线| 欧美性xxxxxx少妇| 东京干手机福利视频| 自拍偷拍亚洲区| 蜜桃视频在线观看免费视频| 国产在线a不卡| 亚洲欧洲免费| 97超碰人人澡| 精品一区二区三区在线观看国产 | 风流少妇一区二区三区91| 在线免费看av不卡| 欧美伦理91| 国产91精品一区二区绿帽| 欧美aaaa视频| 情侣黄网站免费看| 成人精品一区二区三区中文字幕 | 黑人巨大精品欧美一区二区一视频 | 欧美日韩系列| 伊人精品成人久久综合软件| 亚洲综合伊人久久| 国产清纯白嫩初高生在线观看91| 国产无码精品一区二区| 91麻豆精品国产91久久久久久久久 | 国产又爽又黄的激情精品视频 | 九九久久九九久久| 麻豆精品一二三| 黄色片在线观看免费| 日韩欧美在线网址| 天堂在线视频网站| 久久久久久一区二区三区| 日韩不卡在线视频| 亚洲小说欧美另类激情| 麻豆精品视频在线观看| 欧美xxxx精品| 在线免费观看日韩欧美| 欧洲亚洲在线| 欧美最近摘花xxxx摘花| 欧美韩一区二区| 国产精品无码人妻一区二区在线| 国产91丝袜在线播放九色| 欧美色图一区二区| 91精品欧美久久久久久动漫| 黄色免费在线网站| 国产欧亚日韩视频| 亚洲国产精品久久久天堂| 国产精品久久久久久9999| 亚洲欧美日韩国产成人精品影院| 国产精品国产av| 久久天天躁狠狠躁夜夜躁| 麻豆国产一区| 国产91沈先生在线播放| 粉嫩一区二区三区性色av| jizz国产免费| 亚洲精品国产精品久久清纯直播| 黄色在线观看www| 久久艳妇乳肉豪妇荡乳av| 久久久久久黄| 综合 欧美 亚洲日本| 91精品国产91久久久久久一区二区 | 99久久久国产精品无码网爆 | jizz内谢中国亚洲jizz| 欧日韩一区二区三区| 老色鬼精品视频在线观看播放| 日韩欧美国产成人精品免费| 日韩女优视频免费观看| free性护士videos欧美| 欧美视频观看一区| 久久99国产精品尤物| 麻豆亚洲av熟女国产一区二| 亚洲电影免费观看高清完整版在线观看 | 亚洲成人av电影在线| 色久视频在线播放| 国产精品毛片a∨一区二区三区|国| 91亚洲国产高清| 久久久久久久久久久久国产精品| 欧美日韩国产精品| 高清性色生活片在线观看| 成人在线精品视频| 亚洲区国产区| 久久丫精品忘忧草西安产品| 4438x成人网最大色成网站| 国产美女高潮在线| 日韩电影免费观看高清完整| 国产真实精品久久二三区| 日本少妇激情舌吻| 一区二区亚洲精品国产| 6080成人| 亚洲无吗一区二区三区| 亚洲自拍偷拍综合| av电影在线观看| 国产欧美日韩一区二区三区| 蜜臀国产一区二区三区在线播放 | 午夜精产品一区二区在线观看的| 在线不卡欧美精品一区二区三区| a√中文在线观看| 吴梦梦av在线| 久久嫩草精品久久久精品一| 精品国自产拍在线观看| 国产精品电影在线观看| 激情成人综合| 黄色录像免费观看|