日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

徹底搞懂hashMap底層原理

來源: 責(zé)編: 時間:2023-10-18 17:59:05 284觀看
導(dǎo)讀一、說明hashMap在java1.7和java1.8版本中有做一些調(diào)整,我們本篇只說java1.7的hashMap。二、數(shù)據(jù)結(jié)構(gòu)hashMap的數(shù)據(jù)結(jié)構(gòu)是由數(shù)組和鏈表組成,table是一個存放Entry對象的數(shù)組,每個Entry對象由4個屬性組成,分別是key、value

一、說明

hashMap在java1.7和java1.8版本中有做一些調(diào)整,我們本篇只說java1.7的hashMap。o7t28資訊網(wǎng)——每日最新資訊28at.com

o7t28資訊網(wǎng)——每日最新資訊28at.com

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

o7t28資訊網(wǎng)——每日最新資訊28at.com

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

三、屬性信息

先來了解下hashMap中一些重要的屬性:o7t28資訊網(wǎng)——每日最新資訊28at.com

//Hashmap的初始化大小,初始化的值為16,1往右移4位為16static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //HashMap是動態(tài)擴容的,就是容量大小不能大于 1<<30static final int MAXIMUM_CAPACITY = 1 << 30;//默認(rèn)的擴容因子,這個值可以通過構(gòu)造修改static final float DEFAULT_LOAD_FACTOR = 0.75f;//空數(shù)據(jù),默認(rèn)構(gòu)造的時候賦值為空的Entry數(shù)組,在添加元素的時候//會判斷table=EMPTY_TABLE ,然后就擴容static final Entry<?,?>[] EMPTY_TABLE = {};//表示一個空的Hashmaptransient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//Hashmap的大小transient int size;//threshold表示當(dāng)HashMap的size大于threshold時會執(zhí)行resize操作。DEFAULT_INITIAL_CAPACITY=16//擴容的閾值int threshold;//擴容因子,沒有傳入就使用默認(rèn)的DEFAULT_LOAD_FACTOR = 0.75ffinal float loadFactor;//數(shù)據(jù)操作次數(shù),用于迭代檢查修改異常transient int modCount;static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

四、put方法

步驟:o7t28資訊網(wǎng)——每日最新資訊28at.com

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

接下來我們對每一步驟分別展開討論。o7t28資訊網(wǎng)——每日最新資訊28at.com

1.table初始化

private void inflateTable(int toSize) {    // 找到大于等于指定數(shù)組長度的2的n次方    int capacity = roundUpToPowerOf2(toSize); // 擴容閾值,數(shù)組長度*負(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ù)組長度的2的n次方    return number >= MAXIMUM_CAPACITY            ? MAXIMUM_CAPACITY            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;}

數(shù)組初始化其實就三個步驟:計算數(shù)組容量,創(chuàng)建數(shù)組,判斷是否重hash。o7t28資訊網(wǎng)——每日最新資訊28at.com

(1)「數(shù)組容量」: 如果指定數(shù)組長度值大于MAXIMUM_CAPACITY(最大數(shù)組容量:2的30次方),就用最大值;如果指定數(shù)組長度值小于等于1,就用1;如果指定數(shù)組長度值大于1,就用下面的方法得到大于等于指定數(shù)組長度的第一個2的n次方的值。o7t28資訊網(wǎng)——每日最新資訊28at.com

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

這個方法內(nèi)部是通過位移和亦或操作得到的值,感興趣的可以直接看下源碼。o7t28資訊網(wǎng)——每日最新資訊28at.com

(2) 「創(chuàng)建數(shù)組」:直接用計算出來的數(shù)組長度創(chuàng)建Entry數(shù)組table,元素類型為Entry。o7t28資訊網(wǎng)——每日最新資訊28at.com

(3) 「判斷是否重hash」:o7t28資訊網(wǎng)——每日最新資訊28at.com

重hash就是對同個key重新計算hash值,那么為什么要重新計算hash值,實際上只是為了讓hsah值更復(fù)雜,在計算下標(biāo)的時候就會更加散列,減少hash沖突。o7t28資訊網(wǎng)——每日最新資訊28at.com

那么什么樣的條件下才會重hash呢,從源碼可以看出switching為true表示需要重hash,影響switching取值的是下面這段代碼:o7t28資訊網(wǎng)——每日最新資訊28at.com

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

initHashSeedAsNeeded方法用來判斷是否進行重hash,如果需要重hash,會同步更新hash種子,最后返回boolean類型的值。o7t28資訊網(wǎng)——每日最新資訊28at.com

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

Holder.ALTERNATIVE_HASHING_THRESHOLD取的是環(huán)境變量jdk.map.althashing.threshold配置的值(程序員配置),如果沒有配置就默認(rèn)取Integer.MAX_VALUE。o7t28資訊網(wǎng)——每日最新資訊28at.com

通過上面的分析可以知道是否進行重hash只會受到capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD的影響。o7t28資訊網(wǎng)——每日最新資訊28at.com

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

所以說如果程序員不設(shè)置環(huán)境變量,initHashSeedAsNeeded方法實際上沒有意義的。o7t28資訊網(wǎng)——每日最新資訊28at.com

那為什么要更新hash種子呢?o7t28資訊網(wǎng)——每日最新資訊28at.com

這就要了解下hsah值是怎么生成的了:o7t28資訊網(wǎng)——每日最新資訊28at.com

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進行5次亦或操作4次位移運算得到的。o7t28資訊網(wǎng)——每日最新資訊28at.com

所以相同的key只有在hashSeed變化后才有生成不同的hash值,否則生成的永遠(yuǎn)是相同的hsah值,所以需要重hash的時候就必須改變hashSeed,否則重hash的結(jié)果會和原來一樣。o7t28資訊網(wǎng)——每日最新資訊28at.com

2.處理key為null

private V putForNullKey(V value) { // 當(dāng)key為null時,數(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)建一個Entry放入table[0]    addEntry(0, null, value, 0);    return null;}

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

3.計算下標(biāo)

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

計算下標(biāo)實際上分為兩步,計算hsah值和計算下標(biāo),計算下標(biāo)的原理是用hash值除以數(shù)組容量,得到的余數(shù)就是下標(biāo),用這種方式可以保證不同的key一定會放在數(shù)組中的某個桶中,不會越界,而hash值可以讓不同的key在數(shù)組中的分部足夠分散,減少hsah沖突。o7t28資訊網(wǎng)——每日最新資訊28at.com

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方法,這里再來說一次,我們知道java中每個類都默認(rèn)由hashcode方法生成hashcode,但是hashMap中并沒有直接用此方法生成的hashcode,而是對其生成的hahshcode與hash種子進行亦或和位移操作,1.7的hashMap在計算hsah的時候進行了5次亦或操作和4次位移運算,這樣做的目的就是使得不同的key計算出來的hash更加分散,更加能減少hash沖突。o7t28資訊網(wǎng)——每日最新資訊28at.com

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

我們上面說了hash除以數(shù)組容量得到數(shù)組下標(biāo),但是這種做法在java中太慢了,而和此做法同效的一種方式就是h & (length-1),就是數(shù)組容量減去1,再與hash做&操作。這種方式在java中是非常高效的。o7t28資訊網(wǎng)——每日最新資訊28at.com

4.遍歷查找key

運行到這里,數(shù)組已經(jīng)有了,key對應(yīng)的下標(biāo)也有了,接下來就是插入操作,插入之前會先查看下標(biāo)對應(yīng)的桶中是否為空桶,如果不為空桶就要先遍歷查找是否有相同key。o7t28資訊網(wǎng)——每日最新資訊28at.com

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

因為hashMap是由數(shù)組和鏈表組成,數(shù)組的每個桶中都由鏈表組成,所以需要遍歷鏈表查找相同的key,如果存在相同的key就更新覆蓋,如果沒有,就調(diào)用addEntry方法進行插入。o7t28資訊網(wǎng)——每日最新資訊28at.com

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

可以看到在正式添加元素之前會先判斷是否需要擴容,如果需要則先進行擴容。o7t28資訊網(wǎng)——每日最新資訊28at.com

5.擴容處理

從上面的源碼可以知道擴容需要滿足兩個條件:o7t28資訊網(wǎng)——每日最新資訊28at.com

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

如果滿足條件則進入resize方法進行擴容:o7t28資訊網(wǎng)——每日最新資訊28at.com

void resize(int newCapacity) { // 原數(shù)組    Entry[] oldTable = table;    // 原數(shù)組長度    int oldCapacity = oldTable.length;    // 如果原數(shù)組長度為2^30,不進行擴容,把擴容閾值修改為2^31-1    if (oldCapacity == MAXIMUM_CAPACITY) {        threshold = Integer.MAX_VALUE;        return;    } // 根據(jù)新的數(shù)組長度,創(chuàng)建數(shù)組    Entry[] newTable = new Entry[newCapacity];    // 轉(zhuǎn)移數(shù)組數(shù)據(jù)     // 調(diào)用initHashSeedAsNeeded方法,根據(jù)新數(shù)組的長度判斷是否會hashSeed重新賦值    transfer(newTable, initHashSeedAsNeeded(newCapacity));    // table指向新數(shù)組    table = newTable;    // 計算新的擴容閾值    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);}// transfer方法void transfer(Entry[] newTable, boolean rehash) { // 新數(shù)組長度    int newCapacity = newTable.length;    // 遍歷原數(shù)組的Entry    for (Entry<K,V> e : table) {     // Entry不為null        while(null != e) {         // 先找到鏈表中下一個要轉(zhuǎn)移的Entry            Entry<K,V> next = e.next;            // 如果hashSeed變了,重新進行hash計算            if (rehash) {                e.hash = null == e.key ? 0 : hash(e.key);            }            // 重新計算數(shù)組下標(biāo),結(jié)果分兩種:1.與原下標(biāo)一直 2.原下標(biāo)+本次擴容了多長            int i = indexFor(e.hash, newCapacity);            /*原數(shù)組統(tǒng)一鏈表中的數(shù)據(jù)轉(zhuǎn)移到新數(shù)組中時,所處鏈表順序顛倒,因此多線程的情況下可能會出現(xiàn)環(huán)形鏈表問題*/            // Entry的next指向新數(shù)組的鏈表頭            e.next = newTable[i];            // Entry放入新數(shù)組的鏈表中            newTable[i] = e;            // 下一次進行操作的就是原數(shù)組鏈表的下一個            e = next;        }    }}

擴容步驟:o7t28資訊網(wǎng)——每日最新資訊28at.com

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

transfer方法進行具體的擴容處理:o7t28資訊網(wǎng)——每日最新資訊28at.com

實際上就是遍歷舊數(shù)組,從舊數(shù)組的第一個桶中拿到鏈表,從鏈表頭部開始遍歷,一個一個的取出計算新的下標(biāo)(如果需要重hash就會用新的hsah種子計算hsah值,如果不需要重hash就用原來的hsah值,最終用hsah值和新數(shù)組容量計算下標(biāo)),然后頭插法插到新的數(shù)組中。o7t28資訊網(wǎng)——每日最新資訊28at.com

你會發(fā)現(xiàn)兩個規(guī)律:o7t28資訊網(wǎng)——每日最新資訊28at.com

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

我們通過一個例子來分析一下第二條規(guī)律。o7t28資訊網(wǎng)——每日最新資訊28at.com

假設(shè)table.length=16,現(xiàn)在有兩個key,key1對應(yīng)的hash值為68,key2對應(yīng)hash值為84,根據(jù)公式h & (length-1)計算,&運算規(guī)則是遇0為0,結(jié)果如下:o7t28資訊網(wǎng)——每日最新資訊28at.com

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

可見兩個值都落在table[4]這個桶中。o7t28資訊網(wǎng)——每日最新資訊28at.com

擴容一次后table.length=32,再根據(jù)公式h & (length-1)計算結(jié)果如下:o7t28資訊網(wǎng)——每日最新資訊28at.com

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]。o7t28資訊網(wǎng)——每日最新資訊28at.com

再擴容一次后table.length=64,再根據(jù)公式h & (length-1)計算結(jié)果如下:o7t28資訊網(wǎng)——每日最新資訊28at.com

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

可見兩個值還在原來的數(shù)組下標(biāo)對應(yīng)的桶中。o7t28資訊網(wǎng)——每日最新資訊28at.com

結(jié)論:同一個桶中的鏈表中的數(shù)據(jù),擴容后,在新數(shù)組中的下標(biāo)要么和原數(shù)組相同,要么是原數(shù)組下標(biāo)加擴容的長度。o7t28資訊網(wǎng)——每日最新資訊28at.com

這個規(guī)律是怎么形成的呢?o7t28資訊網(wǎng)——每日最新資訊28at.com

這得益于數(shù)組的容量都為2的n次方,數(shù)組每次擴容的后結(jié)果都是原來數(shù)組容量的兩倍,例如:16,32,64...,length-1的結(jié)果分別是15,31,63,而對應(yīng)的二進制如下:o7t28資訊網(wǎng)——每日最新資訊28at.com

0000 11110001 11110011 1111

可以看的出,每次擴容都是高位加1,就導(dǎo)致在計算的時候只需要看hash值中與高位對應(yīng)的那個位上是0還是1,也就導(dǎo)致新數(shù)組中的下標(biāo)只有兩種可能:如果是0下標(biāo)不變,如果是1下標(biāo)就會變化。o7t28資訊網(wǎng)——每日最新資訊28at.com

這個規(guī)律有什么好處呢?o7t28資訊網(wǎng)——每日最新資訊28at.com

這個規(guī)律可以使原來在同個桶中的數(shù)據(jù)可以分散到其他桶中,使得數(shù)組分部更加均勻,減少hash沖突,擴容的時候同個桶中的數(shù)據(jù)將會被分部到新數(shù)組中的哪個桶中,可以通過只判斷高位就能確定,所以可以以此來優(yōu)化擴容效率。o7t28資訊網(wǎng)——每日最新資訊28at.com

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ù)組長度+1    size++;}

頭插法的源碼就很簡單了,就是新建一個Entry對象,新建Entry對象的next屬性指向當(dāng)前坐標(biāo)下的頭部Entry對象,再把新的Entry對象引用賦值給當(dāng)前數(shù)組下標(biāo)處。o7t28資訊網(wǎng)——每日最新資訊28at.com

這里的文字說明可能比自己看源碼更繞。自己看源碼應(yīng)該一目了然。o7t28資訊網(wǎng)——每日最新資訊28at.com

五、問題匯總

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

只有在數(shù)組長度為2的n次方時,數(shù)組長度-1轉(zhuǎn)換為2進制時才可以轉(zhuǎn)換為低位全部為1的二進制,和hash值進行&運算時才能等效于hash值除以數(shù)組容量求余的結(jié)果。o7t28資訊網(wǎng)——每日最新資訊28at.com

2.為什么要用頭插法?

實際上無論是頭插法還是尾插法,都是需要遍歷鏈表的,如果在遍歷過程中找到key相同的,則更新覆蓋,這種情況不會有插入操作,所以無所謂頭插法和尾插法,但是如果沒有找到key相同的元素,那這時肯定已經(jīng)遍歷到鏈表的尾部了,所以但凡插入,不管是頭插法還是尾插法都不會在遍歷鏈表上節(jié)省時間,又因為鏈表的插入僅僅是更換next屬性的指針,所以兩種插入方式的效率是沒有區(qū)別的。o7t28資訊網(wǎng)——每日最新資訊28at.com

java1.7之所以使用頭插法應(yīng)該和自身的代碼結(jié)構(gòu)有關(guān),因為插入方法是獨立的,如果用尾插法,就要在遍歷的時候記錄最后一個元素的值,而頭插法就不需要了,但是我覺得這個不是主要原因,個人覺得java開發(fā)者只不過是兩者選擇了一個而已,沒有什么特殊考慮,否則也不至于會有循環(huán)鏈表的問題了。o7t28資訊網(wǎng)——每日最新資訊28at.com

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

hashMap線程不安全主要表現(xiàn)在兩個方面:o7t28資訊網(wǎng)——每日最新資訊28at.com

(1)多線程插入數(shù)據(jù)的時候,數(shù)據(jù)丟失問題o7t28資訊網(wǎng)——每日最新資訊28at.com

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

上面是頭插法的代碼邏輯,在多線程操作下,如果兩個線程同時走到方法內(nèi)第一行,那么獲取到的e是相同的,然后兩個線程分別創(chuàng)建Entry對象,并且Entry對象的next屬性賦值為e,這樣一來總會有一個線程的數(shù)據(jù)被丟掉了。o7t28資訊網(wǎng)——每日最新資訊28at.com

(2) 多線程情況下擴容的時候可能會發(fā)生循環(huán)鏈表o7t28資訊網(wǎng)——每日最新資訊28at.com

循環(huán)鏈表發(fā)生在多線程擴容的情況下,下面是擴容的部分代碼:o7t28資訊網(wǎng)——每日最新資訊28at.com

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ù)從鏈表頭部一個個利用頭插法插入到新數(shù)組中。o7t28資訊網(wǎng)——每日最新資訊28at.com

假設(shè)有兩個線程同時進行擴容,兩條線程都執(zhí)行到下面這行代碼:o7t28資訊網(wǎng)——每日最新資訊28at.com

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

此時第一條線程繼續(xù)執(zhí)行,第二條線程卡住,等到第一條線程整個循環(huán)執(zhí)行結(jié)束后,第二條線程才繼續(xù)執(zhí)行。o7t28資訊網(wǎng)——每日最新資訊28at.com

此時第一條線程擴容完成,鏈表的指向和原數(shù)組的順序相反。假設(shè)原數(shù)組的某個桶中鏈表方向是1>2>3>4,擴容后又恰好都落在同一個新的桶中,那么新的鏈表方向是4>3>2>1。o7t28資訊網(wǎng)——每日最新資訊28at.com

此時第二條線程開始執(zhí)行循環(huán):o7t28資訊網(wǎng)——每日最新資訊28at.com

  • 第一輪循環(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

此時循環(huán)鏈表出現(xiàn)了。o7t28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-14005-0.html徹底搞懂hashMap底層原理

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 增強現(xiàn)實改變營銷的三種方式

下一篇: Java代碼手撕【數(shù)據(jù)結(jié)構(gòu)】| 隊列的實現(xiàn)與優(yōu)化指南

標(biāo)簽:
  • 熱門焦點
  • 一加Ace2 Pro真機揭曉 鈦空灰配色質(zhì)感拉滿

    終于,在經(jīng)過了幾波預(yù)熱之后,一加Ace2 Pro的外觀真機圖在網(wǎng)上出現(xiàn)了。還是博主數(shù)碼閑聊站曝光的,這次的外觀設(shè)計還是延續(xù)了一加11的方案,只是細(xì)節(jié)上有了調(diào)整,例如新加入了鈦空灰
  • 官方承諾:K60至尊版將會首批升級MIUI 15

    全新的MIUI 15今天也有了消息,在官宣了K60至尊版將會搭載天璣9200+處理器和獨顯芯片X7的同時,Redmi給出了官方承諾,K60至尊重大更新首批升級,會首批推送MIUI 15。也就是說雖然
  • 6月安卓手機好評榜:魅族20 Pro蟬聯(lián)冠軍

    性能榜和性價比榜之后,我們來看最后的安卓手機好評榜,數(shù)據(jù)來源安兔兔評測,收集時間2023年6月1日至6月30日,僅限國內(nèi)市場。第一名:魅族20 Pro好評率:95%5月份的時候魅族20 Pro就是
  • 5月iOS設(shè)備性能榜:M1 M2依舊是榜單前五

    和上個月一樣,沒有新品發(fā)布的iOS設(shè)備性能榜的上榜設(shè)備并沒有什么更替,僅僅只有跑分變化而產(chǎn)生的排名變動,剛剛開始的蘋果WWDC2023,推出的產(chǎn)品也依舊是新款Mac Pro、新款Mac Stu
  • Flowable工作流引擎的科普與實踐

    一.引言當(dāng)我們在日常工作和業(yè)務(wù)中需要進行各種審批流程時,可能會面臨一系列技術(shù)和業(yè)務(wù)上的挑戰(zhàn)。手動處理這些審批流程可能會導(dǎo)致開發(fā)成本的增加以及業(yè)務(wù)復(fù)雜度的上升。在這
  • 2天漲粉255萬,又一賽道在抖音爆火

    來源:運營研究社作者 | 張知白編輯 | 楊佩汶設(shè)計 | 晏談夢潔這個暑期,旅游賽道徹底火了:有的「地方」火了&mdash;&mdash;貴州村超旅游收入 1 個月超過 12 億;有的「博主」火了&m
  • 微博大門常打開,迎接海外畫師漂洋東渡

    作者:互聯(lián)網(wǎng)那些事&ldquo;起猛了,我能看得懂日語了&rdquo;。&ldquo;為什么日本人說話我能聽懂?&rdquo;&ldquo;中文不像中文,日語不像日語,但是我竟然看懂了&rdquo;&hellip;&hell
  • OPPO K11樣張首曝:千元機影像“卷”得真不錯!

    一直以來,OPPO K系列機型都保持著較為均衡的產(chǎn)品體驗,歷來都是2K價位的明星機型,去年推出的OPPO K10和OPPO K10 Pro兩款機型憑借各自的出色配置,堪稱有
  • 電博會與軟博會實現(xiàn)"線下+云端"的雙線融合

    在本次“電博會”與“軟博會”雙展會利好條件的加持下,既可以發(fā)揮展會拉動人流、信息流、資金流實現(xiàn)快速交互流動的作用,繼而推動區(qū)域經(jīng)濟良性發(fā)展;又可以聚
Top 主站蜘蛛池模板: 玛纳斯县| 芜湖县| 绥芬河市| 和硕县| 庆元县| 宣恩县| 榆林市| 定边县| 泾阳县| 南部县| 淅川县| 和平区| 横峰县| 剑阁县| 绥棱县| 台北市| 建宁县| 绥德县| 体育| 宁安市| 新密市| 南部县| 诸城市| 思茅市| 古浪县| 于田县| 塘沽区| 忻城县| 叙永县| 陆丰市| 宜丰县| 布拖县| 封丘县| 龙海市| 冀州市| 淳化县| 丰宁| 额尔古纳市| 县级市| 崇左市| 天柱县|