HashMap的底層實(shí)現(xiàn)原理分析
一、HashMap底層實(shí)現(xiàn)結(jié)構(gòu)
在JDK1.7以前,HashMap的底層數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)是數(shù)組 + 鏈表的實(shí)現(xiàn)方式。
但是在1.8之后HashMap的實(shí)現(xiàn)是數(shù)組 + 鏈表 + 紅黑樹

在了解HashMap實(shí)現(xiàn)原理之前,我們先要知道:
- HashMap數(shù)據(jù)底層具體存儲(chǔ)的是什么?
- 這樣的存儲(chǔ)方式有什么優(yōu)點(diǎn)?
1.1、HashMap數(shù)據(jù)底層具體存儲(chǔ)的是什么
從源碼可知,HashMap類中有一個(gè)非常重要的字段,就是Node[] table,即哈系桶數(shù)組,明顯它是一個(gè)Node 數(shù)組,在JDK1.8中的實(shí)現(xiàn)如下:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //?來定位數(shù)組索引位置
final K key; // // 當(dāng)前節(jié)點(diǎn)的 key
V value; // 當(dāng)前節(jié)點(diǎn)的 value
Node<K,V> next; //鏈表的下?個(gè)node,在整個(gè)節(jié)點(diǎn)對(duì)象中, 僅有一個(gè) next 節(jié)點(diǎn), 說明該鏈表是一個(gè)單向鏈表
Node(int hash, K key, V value, Node<K,V> next) { … }
public final K getKey(){ … }
public final V getValue() { … }
public final String toString() { … }
public final int hashCode() { … }
public final V setValue(V newValue) { … }
public final boolean equals(Object o) { … }
}Node是HashMap的一個(gè)內(nèi)部類,實(shí)現(xiàn)Map.Entry接口,本質(zhì)上就是一個(gè)鍵值對(duì)的類型。
1.2、這樣的存儲(chǔ)方式有什么優(yōu)點(diǎn)
HashMap就是使用哈希表來存儲(chǔ)的。哈希表為解決沖突,可以采用開放地址法和鏈地址法等來解決 問題, Java中HashMap采用了鏈地址法。鏈地址法,簡(jiǎn)單來說,就是數(shù)組加鏈表的結(jié)合。
在每個(gè)數(shù)組元 素上都一個(gè)鏈表結(jié)構(gòu),當(dāng)數(shù)據(jù)被Hash后,得到數(shù)組下標(biāo),把數(shù)據(jù)放在對(duì)應(yīng)下標(biāo)元素的鏈表上。
例如程序 執(zhí)行下面代碼知:
map .put("美團(tuán)" ,"?美");系統(tǒng)將調(diào)用"美團(tuán)"這個(gè)key的hashCode()方法得到其hashCode 值,該方法適用于每個(gè)Java對(duì)象
然后再通過Hash算法的后兩步運(yùn)算樣(高位運(yùn)算和取模運(yùn)算)來定位該鍵值對(duì)的存儲(chǔ)位置,有時(shí)兩個(gè)key會(huì)定位到相同的位置,表示發(fā)生了Hash碰撞。當(dāng)然Hash算法計(jì)算結(jié)果越分散均勻, Hash碰撞的概率就越小,map的存取效率就會(huì)越高。
如果哈希桶數(shù)組很大,即使較差的Hash算法也會(huì)比較分散,如果哈希桶數(shù)組數(shù)組很小,即使好的Hash算法也會(huì)出現(xiàn)較多碰撞,所以就需要在空間成本和時(shí)間成本之間權(quán)衡,其實(shí)就是在根據(jù)實(shí)際情況確定哈希桶數(shù)組的大小,并在此基礎(chǔ)上設(shè)計(jì)好的hash算法減少Hash碰撞。那么通過什么方式來控制map使得Hash碰撞的概率又小,哈希桶數(shù)組(Node[] table)占用空間又少呢?答案就是好的Hash算法和擴(kuò)容機(jī)制。
在理解Hash和擴(kuò)容流程之前,我們得先了解下HashMap的幾個(gè)字段:
int threshold; // 所能容納的key-value對(duì)極限 final float loadFactor; // 負(fù)載因? int modCount; int size;
loadFactor:負(fù)載因子- 默認(rèn)為0.75 ,是決定擴(kuò)容閾值的重要因素之一
threshold:擴(kuò)容的閾值- 擴(kuò)容閾值的計(jì)算公式:
threshold = length * LoadFactor,length為數(shù)組的長(zhǎng)度 - threshold就是在此loadFactor和length(數(shù)組長(zhǎng)度)對(duì)應(yīng)下允許的最大元 素?cái)?shù)目,超過這個(gè)數(shù)目就重新resize(擴(kuò)容) ,擴(kuò)容后的HashMap容量是之前容量的兩倍
modCount:記錄內(nèi)部結(jié)構(gòu)發(fā)生變化的次數(shù)- 內(nèi)部結(jié)構(gòu)發(fā)生變化指的是結(jié)構(gòu)發(fā)生變化,例如put新鍵值 對(duì),但是某個(gè)key對(duì)應(yīng)的value值被覆蓋不屬于結(jié)構(gòu)變化
size:HashMap中存在的鍵值對(duì)數(shù)量- 注意和table的長(zhǎng)度length 、容納 最大鍵值對(duì)數(shù)量threshold的區(qū)別
這里存在一個(gè)問題,即使負(fù)載因子和Hash算法設(shè)計(jì)的再合理,也免不了會(huì)出現(xiàn)拉鏈過長(zhǎng)的情況,一旦出 現(xiàn)拉鏈過長(zhǎng),則會(huì)嚴(yán)重影響HashMap的性能。于是,在JDK1.8版本中,對(duì)數(shù)據(jù)結(jié)構(gòu)做了進(jìn)一步的優(yōu) 化,引入了紅黑樹。而當(dāng)鏈表長(zhǎng)度太長(zhǎng)(默認(rèn)超過8)時(shí),鏈表就轉(zhuǎn)換為紅黑樹,利用紅黑樹快速增刪 改查的特點(diǎn)提高HashMap的性能,其中會(huì)用到紅黑樹的插入、刪除、查找等算法。
二、功能實(shí)現(xiàn)
2.1、確定哈希桶數(shù)組索引位置
不管增加、刪除、查找鍵值對(duì),定位到哈希桶數(shù)組的位置都是很關(guān)鍵的第一步。前面說過HashMap的 數(shù)據(jù)結(jié)構(gòu)是數(shù)組和鏈表的結(jié)合,所以我們當(dāng)然希望這個(gè)HashMap里面的元素位置盡量分布均勻些,盡 量使得每個(gè)位置上的元素?cái)?shù)量只有一個(gè),那么當(dāng)我們用hash算法求得這個(gè)位置的時(shí)候,馬上就可以知道 對(duì)應(yīng)位置的元素就是我們要的,不用遍歷鏈表,大大優(yōu)化了查詢的效率。 HashMap定位數(shù)組索引位 置,直接決定了hash方法的離散性能。
先看看源碼的實(shí)現(xiàn)(方法一+方法二):
?法?:
static final int hash(Object key) { //jdk1.8 & jdk1.7
int h;
// h = key.hashCode() 為第?步 取hashCode值
// h ^ (h >>> 16) 為第?步 ?位參與運(yùn)算
// 1001 0111 0011 0101 1101 1110 1001 1111 => hash code
// 0000 0000 0000 0000 1001 0111 0011 0101 => 右移 16 位
// 1001 0111 0011 0101 0100 1001 1010 1010 => 異或運(yùn)算
// 讓同一個(gè) key 的高位與地位都參與 hash 運(yùn)算
// 目的: 降低 hash 碰撞的概率
// 最終返回一個(gè) 異或運(yùn)算 后的一個(gè)更混亂的 hash 值
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
?法?:
static int indexFor(int h, int length) { //jdk1.7的源碼,jdk1.8沒有這個(gè)?法,但是實(shí)現(xiàn)原理?樣的
return h & (length-1); //第三步 取模運(yùn)算
}對(duì)于任意給定的對(duì)象,只要它的hashCode()返回值相同,那么程序調(diào)用方法一所計(jì)算得到的Hash碼值總是相同的。我們首先想到的就是把hash值對(duì)數(shù)組長(zhǎng)度取模運(yùn)算,這樣一來,元素的分布相對(duì)來說是比較均勻的。但是,模運(yùn)算的消耗還是比較大的,在HashMap中是這樣做的:調(diào)用方法二來計(jì)算該對(duì)象 應(yīng)該保存在table數(shù)組的哪個(gè)索引處。
這個(gè)方法非常巧妙,它通過h & (table.length -1)來得到該對(duì)象的保存位,而HashMap底層數(shù)組的長(zhǎng)度總 是2的n次方,這是HashMap在速度上的優(yōu)化。當(dāng)length總是2的n次方時(shí), h& (length-1)運(yùn)算等價(jià)于對(duì) length取模,也就是h%length ,但是&比%具有更高的效率。
在JDK1.8的實(shí)現(xiàn)中,優(yōu)化了高位運(yùn)算的算法,通過hashCode()的高16位異或低16位實(shí)現(xiàn)的知(h = k.hashCode()) ^ (h >>> 16),主要是從速度、功效、質(zhì)量來考慮的,這么做可以在數(shù)組table的length 比較小的時(shí)候,也能保證考慮到高低Bit都參與到Hash的計(jì)算中,同時(shí)不會(huì)有太大的開銷。
下面舉例說明下, n為table的長(zhǎng)度:

2.2、HashMap的put方法
HashMap的put方法執(zhí)行過程可以通過下圖來理解:

下面是JDK1.8 的HashMap put方法源碼
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}實(shí)際上是putVal方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K, V>[] tab; // Node 數(shù)組 => hash 表, 額外的變量
Node<K, V> p; // 當(dāng)前 key 存放索引位置的元素
int n, // table 數(shù)組的長(zhǎng)度
i; // 當(dāng)前 key 經(jīng)過運(yùn)算后的索引位置 => (長(zhǎng)度 - 1) & hash
// 如果 table 數(shù)組為 null 或數(shù)組的長(zhǎng)度為 0, 就進(jìn)行擴(kuò)容 => 初始化
if ((tab = table) == null || (n = tab.length) == 0)
// 第一次擴(kuò)容實(shí)際就是 table 的初始化
n = (tab = resize()).length;
// 索引 = (長(zhǎng)度 - 1) & hash
// 例子:
// hash = 1001 0111 0011 0101 0100 1001 1010 1010
// length - 1 = 0000 0000 0000 0000 0000 0000 0000 1111
// 索引 = 0000 0000 0000 0000 0000 0000 0000 1010
// 計(jì)算索引位置, 并訪問該索引位置得到節(jié)點(diǎn)賦值給 p, 且判斷 p 是否為 null
if ((p = tab[i = (n - 1) & hash]) == null) // 說明索引位置沒有值
// 如果該索引位置沒有值, 就直接創(chuàng)建一個(gè)新的節(jié)點(diǎn), 并將其存在這個(gè)位置上
tab[i] = newNode(hash, key, value, null);
else { // 如果這個(gè)索引位置已經(jīng)有值
Node<K, V> e; // 上一個(gè)相同 key 的節(jié)點(diǎn)對(duì)象, 與本次存入的新的 key 完全相同的 Node 對(duì)象
K k; // 當(dāng)前索引位置上的 key
// 判斷當(dāng)前索引位置的元素與新存入的 key 是否完全相同
// 第一種情況: 直接拿頭節(jié)點(diǎn)進(jìn)行比較, 不關(guān)心他是樹節(jié)點(diǎn), 還是鏈表節(jié)點(diǎn)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 如果這個(gè)位置上的 key 與即將存入的 key 完全相同, 就直接將當(dāng)前節(jié)點(diǎn)賦值給 e
e = p;
// 第二種情況: 判斷是否是樹節(jié)點(diǎn), 也就是判斷當(dāng)前節(jié)點(diǎn)是否已經(jīng)發(fā)生過 hash 沖突, 且已經(jīng)變成紅黑樹
else if (p instanceof TreeNode) // 判斷當(dāng)前節(jié)點(diǎn)是否已經(jīng)變成樹節(jié)點(diǎn) => 判斷是否已經(jīng)變成紅黑樹
// 往紅黑樹中存入當(dāng)前新的 key/value, 并返回存入后的對(duì)象賦值給 e
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
else {
// 第三種情況: 如果以上兩者都不是, 就默認(rèn)為鏈表, 判斷是否有下一節(jié)點(diǎn), 進(jìn)行循環(huán)判斷
for (int binCount = 0; ; ++binCount) { // 計(jì)數(shù), 計(jì)算鏈表的長(zhǎng)度
// 判斷當(dāng)前節(jié)點(diǎn)是否有下一個(gè)節(jié)點(diǎn), 如果為 null
if ((e = p.next) == null) {
// 將即將存入的 key/value 存入一個(gè)新的節(jié)點(diǎn), 并設(shè)置到當(dāng)前節(jié)點(diǎn)的 next
p.next = newNode(hash, key, value, null);
// 判斷當(dāng)前鏈表長(zhǎng)度是否大于樹化的閾值(7)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 鏈表長(zhǎng)度 >= 8, 那么就準(zhǔn)備進(jìn)行轉(zhuǎn)成樹
treeifyBin(tab, hash);
break; // 這次循環(huán)結(jié)束
}
// 此時(shí) e = 當(dāng)前正在遍歷的鏈表元素
// 判斷該元素的 hash 與 key 是否與即將存入的 key 完全相同
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; // 如果相同, 直接結(jié)束循環(huán)
p = e; // 將當(dāng)前遍歷的節(jié)點(diǎn), 賦值給之前索引位置的節(jié)點(diǎn)
}
}
// 如果找到了與本次要存入的 key 完全相同的節(jié)點(diǎn), 直接將本次存入新的 value 替換舊節(jié)點(diǎn)的 value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) // 如果配置了只有在不存在時(shí)才存入 或 原來的值為 空
e.value = value; // 將新的 value 覆蓋原來舊的值
afterNodeAccess(e);
return oldValue;
}
}
++modCount; // 并發(fā)修改數(shù)的記錄
if (++size > threshold) // 存入鍵值對(duì)的數(shù)量+1, 且判斷是否大于擴(kuò)容閾值
resize(); // 擴(kuò)容
afterNodeInsertion(evict);
return null;
}2.3、HashMap的擴(kuò)容原理
擴(kuò)容(resize)就是重新計(jì)算容量,向HashMap對(duì)象里不停的添加元素,而HashMap對(duì)象內(nèi)部的數(shù)組無法裝載更多的元素時(shí),對(duì)象就需要擴(kuò)大數(shù)組的長(zhǎng)度,以便能裝入更多的元素。當(dāng)然Java里的數(shù)組是無法自動(dòng)擴(kuò)容的,方法是使用一個(gè)新的數(shù)組代替已有的容量小的數(shù)組,就像我們用一個(gè)小桶裝水,如果想裝更 多的水,就得換大水桶。
接下來就是JDK1.8的有關(guān)HashMap擴(kuò)容的源碼:
final Node<K, V>[] resize() {
// 將當(dāng)前 Map 中的 hash 表保存到 oldTab 變量
Node<K, V>[] oldTab = table;
// 再基于舊的數(shù)組長(zhǎng)度得到容量, 如果舊的數(shù)組為 null(剛 new 的對(duì)象, 還沒初始化數(shù)組), 返回容量為 0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 將當(dāng)前的擴(kuò)容閾值作為舊的擴(kuò)容閾值
int oldThr = threshold;
// 用于存儲(chǔ)新的容量以及擴(kuò)容閾值
int newCap, newThr = 0;
// 已經(jīng)初始化: 舊的容量肯定大于 0
if (oldCap > 0) {
// 判斷舊的容量是否已經(jīng)大于最大容量
if (oldCap >= MAXIMUM_CAPACITY) {
// 如果大于, 就將閾值設(shè)置為 Integer 的最大值
threshold = Integer.MAX_VALUE;
// 并且直接返回舊的數(shù)組
return oldTab;
/**
* 下方計(jì)算擴(kuò)容后的新容量以及新閾值
* 新的容量 = 舊的容量左移 1 位 === 舊的容量 * 2
* 判斷: 如果新容量 < 最大容量 AND 舊容量 >= 16
* 如果判斷成立, 閾值也是 * 2
*/
} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY) //
// 新的閾值為舊閾值的兩倍
newThr = oldThr << 1; // double threshold
}
/**
* 如果跳過上面的 if 沒有進(jìn)入, 走到下方任意一個(gè)邏輯, 都代表當(dāng)前的 table 沒有被初始化過
* 1. new HashMap(容量, 加載因子)
* 2. new HashMap(容量)
*/
else if (oldThr > 0) // initial capacity was placed in threshold
// 將舊的閾值作為新的容量
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// new HashMap() => 進(jìn)入這個(gè)邏輯
// 默認(rèn)新的容量為 16
newCap = DEFAULT_INITIAL_CAPACITY;
// 新的閾值 = 默認(rèn)的負(fù)載因子(0.75) * 默認(rèn)容量(16)
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 如果在上方的 if 判斷中, 進(jìn)入的是中間的邏輯, 那么就會(huì)進(jìn)入下方的邏輯
if (newThr == 0) {
// 臨時(shí)閾值 = 新的容量 * 負(fù)載因子
float ft = (float) newCap * loadFactor;
// 新的閾值或新的容量 > 最大值, 就返回 Integer 的最大值
// 否則就為上面計(jì)算的臨時(shí)閾值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
// 將計(jì)算后的新閾值, 覆蓋當(dāng)前 map 中的閾值變量
threshold = newThr;
// 基于新的容量創(chuàng)建一個(gè)新的 Node 數(shù)組
@SuppressWarnings({"rawtypes", "unchecked"})
Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
// 直接將新創(chuàng)建的數(shù)組覆蓋當(dāng)前 map 中的數(shù)組
table = newTab;
// 已經(jīng)初始化過了, 肯定就不會(huì)為 null, 同樣也說明如果未初始化, 該邏輯就不會(huì)執(zhí)行
if (oldTab != null) {
// 對(duì)數(shù)組舊的容量進(jìn)行遍歷
for (int j = 0; j < oldCap; ++j) {
// e == 當(dāng)前遍歷的元素
Node<K, V> e;
if ((e = oldTab[j]) != null) {
// 將當(dāng)前位置重置為 null
oldTab[j] = null;
// 判斷當(dāng)前是否為一個(gè)鏈表, 如果沒有下一個(gè)說明該元素就是單個(gè)節(jié)點(diǎn), 還未變成鏈表
if (e.next == null)
// 重新計(jì)算該元素在新數(shù)組中的索引位置, 并將該元素存入新數(shù)組
newTab[e.hash & (newCap - 1)] = e;
// 如果當(dāng)前的 e 已經(jīng)是紅黑樹的根節(jié)點(diǎn)
else if (e instanceof TreeNode)
// 如果是紅黑樹, 就將這棵樹做切割, 如果切割后單顆樹的長(zhǎng)度 < 6 會(huì)重新轉(zhuǎn)換為鏈表
((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
else { // preserve order
// 既不是單個(gè)節(jié)點(diǎn), 也不是紅黑樹, 就一定是鏈表
// 對(duì)鏈表進(jìn)行拆分, 拆成高位鏈表與地位鏈表兩個(gè)
// 下方定義的變量為低位鏈表的 頭/尾 節(jié)點(diǎn)
Node<K, V> loHead = null, loTail = null;
// 下方定義的變量為高位鏈表的 頭/尾 節(jié)點(diǎn)
Node<K, V> hiHead = null, hiTail = null;
// 臨時(shí)變量, 用于作為遍歷鏈表時(shí)的下一個(gè)節(jié)點(diǎn)
Node<K, V> next;
do {
next = e.next;
// 將當(dāng)前元素的 hash 與舊數(shù)組長(zhǎng)度做 & 運(yùn)算
// 實(shí)際是在拿 hash 的第5位數(shù)與舊數(shù)組長(zhǎng)度的第五位數(shù)做 & 運(yùn)算
// 最終得到的結(jié)果, 要么就是 0, 要么就是 1
// 如果最終結(jié)果為 0: 那么該元素就會(huì)被存入低位鏈表
// 如果最終結(jié)果為 1: 那么該元素就會(huì)被存入高位鏈表
if ((e.hash & oldCap) == 0) {
if (loTail == null) // 如果當(dāng)前鏈表還沒初始化, 意味著尾節(jié)點(diǎn)不存在
loHead = e; // 如果尾節(jié)點(diǎn)不存在, 那么基于尾插法, 就是將當(dāng)前節(jié)點(diǎn)直接作為低位鏈表的頭節(jié)點(diǎn)
else
loTail.next = e; // 如果已經(jīng)初始化過了, 就將當(dāng)前元素直接插入到鏈表的最后
loTail = e; // 并且將當(dāng)前節(jié)點(diǎn)設(shè)置為尾節(jié)點(diǎn)
} else {
if (hiTail == null) // 如果當(dāng)前尾節(jié)點(diǎn)不存在, 說明鏈表還沒初始化
hiHead = e; // 因此將當(dāng)前節(jié)點(diǎn)作為頭節(jié)點(diǎn)
else
hiTail.next = e; // 如果已經(jīng)初始化了, 就將當(dāng)前節(jié)點(diǎn)作為鏈表的最后一個(gè)節(jié)點(diǎn)
hiTail = e; // 同時(shí)由于是尾插法, 索引每次移動(dòng)的這個(gè)元素一定是鏈表的最后一個(gè)元素, 因此直接作為尾節(jié)點(diǎn)
}
} while ((e = next) != null); // 如果還有下一個(gè)節(jié)點(diǎn), 就繼續(xù)遍歷
if (loTail != null) { // 判斷低位的尾不為空 ==> 實(shí)際就是在判斷低位鏈表是有值的
loTail.next = null; // 將尾的下一個(gè)設(shè)置為 null
newTab[j] = loHead; // 將低位鏈表的頭節(jié)點(diǎn), 重新連接到新數(shù)組的當(dāng)前位置
}
if (hiTail != null) { // 如果高位鏈表為空 ==> 實(shí)際就是在判斷高位鏈表是有值的
hiTail.next = null; // 將高位鏈表的尾節(jié)點(diǎn)設(shè)置為 空
newTab[j + oldCap] = hiHead; // 將高位鏈表的頭節(jié)點(diǎn)設(shè)置到新數(shù)組的當(dāng)前位置 + 舊的長(zhǎng)度的位置去
}
}
}
}
}
return newTab;
}當(dāng)初看源碼的時(shí)候不清楚這里為什么要將鏈表拆分為高位鏈表和低位鏈表

后面查資料才發(fā)現(xiàn)HashMap在擴(kuò)容的時(shí)候是擴(kuò)容到原來的2倍,所以,元素的位置要么在原位置,要么就是根據(jù)原位置再移動(dòng)2次冪的位置(也就是原位置兩倍的地方),所以就是因?yàn)橛羞@兩個(gè)區(qū)別,所以在對(duì)元素重新進(jìn)行索引的計(jì)算的時(shí)候分了高位鏈表和低位鏈表。
而元素在重新計(jì)算hash之后,因?yàn)閚變?yōu)?倍,那么n-1的mask范圍在高位多1bit(紅色) ,因此新的index就會(huì)發(fā)生這樣的變化:

因此,我們?cè)跀U(kuò)充HashMap的時(shí)候,不需要像JDK1.7的實(shí)現(xiàn)那樣重新計(jì)算hash ,只需要看看原來的 hash值新增的那個(gè)bit是1還是0就好了,是0的話索引沒變,是1的話索引變成原索引+oldCap

總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決IDEA集成Docker插件后出現(xiàn)日志亂碼的問題
這篇文章主要介紹了解決IDEA集成Docker插件后出現(xiàn)日志亂碼的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-11-11
簡(jiǎn)單了解Java關(guān)鍵字throw和throws的區(qū)別
這篇文章主要介紹了簡(jiǎn)單了解Java關(guān)鍵字throw和throws的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
SpringBoot @PropertySource與@ImportResource有什么區(qū)別
這篇文章主要介紹了SpringBoot @PropertySource與@ImportResource有什么區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01
Java Volatile應(yīng)用單例模式實(shí)現(xiàn)過程解析
這篇文章主要介紹了Java Volatile應(yīng)用單例模式實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
MybatisPlus將自定義的sql列表查詢返回改為分頁查詢
本文主要介紹了MybatisPlus將自定義的sql列表查詢返回改為分頁查詢,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-04-04

