前文:HashMap是Java程序員最常用的映射(鍵值對(duì))處理數(shù)據(jù)的容器。隨著JDK版本的更新,1.8相較于1.7來(lái)說(shuō)又引入了紅黑樹(shù)和擴(kuò)容優(yōu)化等底層優(yōu)化內(nèi)容。
1.部分容器的繼承關(guān)系:

1.HashMap:基于哈希表的Map接口的實(shí)現(xiàn)。此實(shí)現(xiàn)提供所有可選的映射操作,并允許使用null值和null鍵,但是鍵值只能有一個(gè)為null。(除了非同步和允許使用 null 之外,HashMap類(lèi)與Hashtable大致相同。)此類(lèi)不保證映射的順序,特別是它不保證該順序恒久不變。
? ? ? ?此實(shí)現(xiàn)假定哈希函數(shù)將元素適當(dāng)?shù)胤植荚诟魍爸g,可為基本操作(get和put)提供穩(wěn)定的性能。迭代 collection 視圖所需的時(shí)間與HashMap實(shí)例的“容量”(桶的數(shù)量)及其大小(鍵-值映射關(guān)系數(shù))成比例。所以,如果迭代性能很重要,則不要將初始容量設(shè)置得太高(或?qū)⒓虞d因子設(shè)置得太低)。
? ? ? ?HashMap的實(shí)例有兩個(gè)參數(shù)影響其性能:初始容量和加載因子。容量是哈希表中桶的數(shù)量,初始容量只是哈希表在創(chuàng)建時(shí)的容量。加載因子是哈希表在其容量自動(dòng)增加之前可以達(dá)到多滿(mǎn)的一種尺度。當(dāng)哈希表中的條目數(shù)超出了加載因子與當(dāng)前容量的乘積時(shí),則要對(duì)該哈希表進(jìn)行rehash操作(即重建內(nèi)部數(shù)據(jù)結(jié)構(gòu)),從而哈希表將具有大約兩倍的桶數(shù)。
2.Hashtable:Hashtable是遺留類(lèi),很多映射的常用功能與HashMap類(lèi)似,不同的是它承自Dictionary類(lèi),并且是線(xiàn)程安全的,任一時(shí)間只有一個(gè)線(xiàn)程能寫(xiě)Hashtable,并發(fā)性不如ConcurrentHashMap,因?yàn)镃oncurrentHashMap引入了分段鎖。Hashtable不建議在新代碼中使用,不需要線(xiàn)程安全的場(chǎng)合可以用HashMap替換,需要線(xiàn)程安全的場(chǎng)合可以用ConcurrentHashMap替換。
3.LinkedHashMap:LinkedHashMap是HashMap的一個(gè)子類(lèi),保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時(shí),先得到的記錄肯定是先插入的,也可以在構(gòu)造時(shí)帶參數(shù),按照訪(fǎng)問(wèn)次序排序。
4.TreeMap:TreeMap實(shí)現(xiàn)SortedMap接口,能夠把它保存的記錄根據(jù)鍵排序,默認(rèn)是按鍵值的升序排序,也可以指定排序的比較器,當(dāng)用Iterator遍歷TreeMap時(shí),得到的記錄是排過(guò)序的。如果使用排序的映射,建議使用TreeMap。在使用TreeMap時(shí),key必須實(shí)現(xiàn)Comparable接口或者在構(gòu)造TreeMap傳入自定義的Comparator,否則會(huì)在運(yùn)行時(shí)拋出java.lang.ClassCastException類(lèi)型的異常。
5.WeakHashMap:若鍵映射,允許釋放映射所指的對(duì)象,這是為了解決某類(lèi)特殊問(wèn)題而設(shè)計(jì)的。如果映射之外沒(méi)有引用指向某個(gè)鍵,則鍵可以被垃圾回收器收集。
對(duì)于上述四種Map類(lèi)型的類(lèi),要求映射中的key是不可變對(duì)象。不可變對(duì)象是該對(duì)象在創(chuàng)建后它的哈希值不會(huì)被改變。如果對(duì)象的哈希值發(fā)生變化,Map對(duì)象很可能就定位不到映射的位置了。
2.HashMap底層實(shí)現(xiàn)原理:
? ? HashMap底層是由數(shù)組加鏈表或是紅黑樹(shù)的形式實(shí)現(xiàn)的。

先來(lái)看看HashMap中存的是什么:
? ?HashMap中存的是Node鍵值對(duì),Node是HashMap中的一個(gè)內(nèi)部類(lèi)(HashTable中存的是Entry雖然名字不同但是屬性方法基本一樣),Node/Entry實(shí)現(xiàn)了Map.Entry接口其本質(zhì)就是鍵值對(duì)。
? ? HashMap使用哈希表和數(shù)組來(lái)存儲(chǔ)和維護(hù)數(shù)據(jù),每個(gè)數(shù)組元素上都有一個(gè)鏈表。當(dāng)Node需要被put進(jìn)HashMap中時(shí),首先會(huì)獲得鍵值(Key)的HashCode()方法返回值,再將返回值進(jìn)行hash,得出數(shù)組下標(biāo)。(注:有關(guān)HashCode()和equals()方法重寫(xiě)的內(nèi)容可以看我的另一篇文章淺談HashCode()和equals()。HashMap中的哈希方法下文中會(huì)詳細(xì)講解。)得出下標(biāo)后仔進(jìn)行鏈表的儲(chǔ)存。
接下來(lái)我們就進(jìn)入源碼:
HashMap中的屬性:
static final intDEFAULT_INITIAL_CAPACITY=1<<4;//默認(rèn)初始長(zhǎng)度為16
static final floatDEFAULT_LOAD_FACTOR=0.75f;//負(fù)載因子默認(rèn)為0.75,一般不需要改變
transient int size;//HashMap中實(shí)際存儲(chǔ)的鍵值對(duì)數(shù)量。
int threshold;//所能容納的key-value對(duì)極限
? ? ? Node[] table的初始化長(zhǎng)度length(默認(rèn)值是16),Load factor為負(fù)載因子(默認(rèn)值是0.75),threshold是HashMap所能容納的最大數(shù)據(jù)量的Node(鍵值對(duì))個(gè)數(shù)。threshold = length * Load factor。也就是說(shuō),在數(shù)組定義好長(zhǎng)度之后,負(fù)載因子越大,所能容納的鍵值對(duì)個(gè)數(shù)越多。
? ? ? 結(jié)合負(fù)載因子的定義公式可知,threshold就是在此Load factor和length(數(shù)組長(zhǎng)度)對(duì)應(yīng)下允許的最大元素?cái)?shù)目,超過(guò)這個(gè)數(shù)目就重新resize(擴(kuò)容),擴(kuò)容后的HashMap容量是之前容量的兩倍。默認(rèn)的負(fù)載因子0.75是對(duì)空間和時(shí)間效率的一個(gè)平衡選擇,建議大家不要修改,除非在時(shí)間和空間比較特殊的情況下,如果內(nèi)存空間很多而又對(duì)時(shí)間效率要求很高,可以降低負(fù)載因子Load factor的值;相反,如果內(nèi)存空間緊張而對(duì)時(shí)間效率要求不高,可以增加負(fù)載因子loadFactor的值,這個(gè)值可以大于1。
? ? ? ? size這個(gè)字段其實(shí)很好理解,就是HashMap中實(shí)際存在的鍵值對(duì)數(shù)量。注意和table的長(zhǎng)度length、容納最大鍵值對(duì)數(shù)量threshold的區(qū)別。而modCount字段主要用來(lái)記錄HashMap內(nèi)部結(jié)構(gòu)發(fā)生變化的次數(shù),主要用于迭代的快速失敗。強(qiáng)調(diào)一點(diǎn),內(nèi)部結(jié)構(gòu)發(fā)生變化指的是結(jié)構(gòu)發(fā)生變化,例如put新鍵值對(duì),但是某個(gè)key對(duì)應(yīng)的value值被覆蓋不屬于結(jié)構(gòu)變化。
? ? ? ? 一般來(lái)說(shuō)Hash類(lèi)的容器中桶的數(shù)量是素?cái)?shù),如HashTable桶的初始化大小就是11。因?yàn)橄鄬?duì)來(lái)說(shuō)素?cái)?shù)導(dǎo)致沖突的概率要小于合數(shù)。但是HashMap中桶的大小卻是必須是2的n次方。HashMap采用這種非常規(guī)設(shè)計(jì),主要是為了在取模和擴(kuò)容時(shí)做優(yōu)化,同時(shí)為了減少?zèng)_突,HashMap定位哈希桶索引位置時(shí),也加入了高位參與運(yùn)算的過(guò)程。
功能實(shí)現(xiàn):
1.確定哈希桶數(shù)組位置索引:不管是查找、增加還是刪除,定位哈希桶的位置總是第一步,那么定位功能的實(shí)現(xiàn)就對(duì)HashMap的性能起了很大作用。
方法一:
static final int hash(Object key) {
int h;
return(key ==null) ?0: (h = key.hashCode()) ^ (h >>>16);
}
方法二:
static int indexFor(int h, int length) {? //jdk1.7的源碼,jdk1.8沒(méi)有這個(gè)方法,但是實(shí)現(xiàn)原理一樣的
return h & (length-1);? //第三步 取模運(yùn)算
}
這里的Hash算法本質(zhì)上就是三步:取key的hashCode值、高位運(yùn)算、取模運(yùn)算。
方法二中,它通過(guò)h & (table.length -1)來(lái)得到該對(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中的方法,優(yōu)化了高位運(yùn)算的算法,通過(guò)hashCode()的高16位異或低16位實(shí)現(xiàn)的:(h = k.hashCode()) ^ (h >>> 16),主要是從速度、功效、質(zhì)量來(lái)考慮的,這么做可以在數(shù)組table的length比較小的時(shí)候,也能保證考慮到高低Bit都參與到Hash的計(jì)算中,同時(shí)不會(huì)有太大的開(kāi)銷(xiāo)。(JDK1.8也會(huì)對(duì)最后結(jié)果取模運(yùn)算只不過(guò)該過(guò)程在具體的put、get方法中完成)如圖

2.put方法的實(shí)現(xiàn):

①.判斷鍵值對(duì)數(shù)組table[i]是否為空或?yàn)閚ull,否則執(zhí)行resize()進(jìn)行擴(kuò)容;
②.根據(jù)鍵值key計(jì)算hash值得到插入的數(shù)組索引i,如果table[i]==null,直接新建節(jié)點(diǎn)添加,轉(zhuǎn)向⑥,如果table[i]不為空,轉(zhuǎn)向③;
③.判斷table[i]的首個(gè)元素是否和key一樣,如果相同直接覆蓋value,否則轉(zhuǎn)向④,這里的相同指的是hashCode以及equals;
④.判斷table[i] 是否為treeNode,即table[i] 是否是紅黑樹(shù),如果是紅黑樹(shù),則直接在樹(shù)中插入鍵值對(duì),否則轉(zhuǎn)向⑤;
⑤.遍歷table[i],判斷鏈表長(zhǎng)度是否大于8,大于8的話(huà)把鏈表轉(zhuǎn)換為紅黑樹(shù),在紅黑樹(shù)中執(zhí)行插入操作,否則進(jìn)行鏈表的插入操作;遍歷過(guò)程中若發(fā)現(xiàn)key已經(jīng)存在直接覆蓋value即可;
⑥.插入成功后,判斷實(shí)際存在的鍵值對(duì)數(shù)量size是否超多了最大容量threshold,如果超過(guò),進(jìn)行擴(kuò)容。
JDK1.8HashMap的put方法源碼如下:
1 public V put(K key, V value) {
2? ?? // 對(duì)key的hashCode()做hash
3? ?? return putVal(hash(key), key, value, false, true);
4 }
5
6 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
7? ? ? ? ? ? ? ? boolean evict) {
8? ?? Node[] tab; Node p; int n, i;
9? ?? // 步驟①:tab為空則創(chuàng)建
10? ?? if ((tab = table) == null || (n = tab.length) == 0)
11? ? ? ?? n = (tab = resize()).length;
12? ?? // 步驟②:計(jì)算index,并對(duì)null做處理
13? ?? if ((p = tab[i = (n - 1) & hash]) == null)
14? ? ? ?? tab[i] = newNode(hash, key, value, null);
15? ?? else {
16? ? ? ?? Node e; K k;
17? ? ? ?? // 步驟③:節(jié)點(diǎn)key存在,直接覆蓋value
18? ? ? ?? if (p.hash == hash &&
19? ? ? ? ? ?? ((k = p.key) == key || (key != null && key.equals(k))))
20? ? ? ? ? ?? e = p;
21? ? ? ?? // 步驟④:判斷該鏈為紅黑樹(shù)
22? ? ? ?? else if (p instanceof TreeNode)
23? ? ? ? ? ?? e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
24? ? ? ?? // 步驟⑤:該鏈為鏈表
25? ? ? ?? else {
26? ? ? ? ? ?? for (int binCount = 0; ; ++binCount) {
27? ? ? ? ? ? ? ?? if ((e = p.next) == null) {
28? ? ? ? ? ? ? ? ? ?? p.next = newNode(hash, key,value,null);
//鏈表長(zhǎng)度大于8轉(zhuǎn)換為紅黑樹(shù)進(jìn)行處理
29? ? ? ? ? ? ? ? ? ?? if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
30? ? ? ? ? ? ? ? ? ? ? ?? treeifyBin(tab, hash);
31? ? ? ? ? ? ? ? ? ?? break;
32? ? ? ? ? ? ? ?? }
// key已經(jīng)存在直接覆蓋value
33? ? ? ? ? ? ? ?? if (e.hash == hash &&
34? ? ? ? ? ? ? ? ? ?? ((k = e.key) == key || (key != null && key.equals(k))))
35? ? ? ? ? ? ? ? ? ? ? ? ? ? break;
36? ? ? ? ? ? ? ?? p = e;
37? ? ? ? ? ?? }
38? ? ? ?? }
39
40? ? ? ?? if (e != null) { // existing mapping for key
41? ? ? ? ? ?? V oldValue = e.value;
42? ? ? ? ? ?? if (!onlyIfAbsent || oldValue == null)
43? ? ? ? ? ? ? ?? e.value = value;
44? ? ? ? ? ?? afterNodeAccess(e);
45? ? ? ? ? ?? return oldValue;
46? ? ? ?? }
47? ?? }
48? ?? ++modCount;
49? ?? // 步驟⑥:超過(guò)最大容量 就擴(kuò)容
50? ?? if (++size > threshold)
51? ? ? ?? resize();
52? ?? afterNodeInsertion(evict);
53? ?? return null;
54 }
3.resize擴(kuò)容機(jī)制:當(dāng)原本的HashMap無(wú)法容納更多的Node時(shí)就需要擴(kuò)容
由于JDK1.8對(duì)該部分做了一些優(yōu)化我們先研究JDK1.7的源碼再來(lái)看看1.8的優(yōu)化:
1 void resize(int newCapacity) {? //傳入新的容量
2? ?? Entry[] oldTable = table;? ? //引用擴(kuò)容前的Entry數(shù)組
3? ?? int oldCapacity = oldTable.length;
4? ?? if (oldCapacity == MAXIMUM_CAPACITY) {? //擴(kuò)容前的數(shù)組大小如果已經(jīng)達(dá)到最大(2^30)了
5? ? ? ?? threshold = Integer.MAX_VALUE; //修改閾值為int的最大值(2^31-1),這樣以后就不會(huì)擴(kuò)容了
6? ? ? ?? return;
7? ?? }
8
9? ?? Entry[] newTable = new Entry[newCapacity];? //初始化一個(gè)新的Entry數(shù)組
10? ?? transfer(newTable);? ? ? ? ? ? ? ? ? ? ? ?? //?。?shù)據(jù)轉(zhuǎn)移到新的Entry數(shù)組里
11? ?? table = newTable;? ? ? ? ? ? ? ? ? ? ? ? ?? //HashMap的table屬性引用新的Entry數(shù)組
12? ?? threshold = (int)(newCapacity * loadFactor);//修改閾值
13 }
這里就是使用一個(gè)容量更大的數(shù)組來(lái)代替已有的容量小的數(shù)組,transfer()方法將原有Entry數(shù)組的元素拷貝到新的Entry數(shù)組里。
1 void transfer(Entry[] newTable) {
2? ?? Entry[] src = table;? ? ? ? ? ? ? ? ?? //src引用了舊的Entry數(shù)組
3? ?? int newCapacity = newTable.length;
4? ?? for (int j = 0; j < src.length; j++) { //遍歷舊的Entry數(shù)組
5? ? ? ?? Entry e = src[j];? ? ? ? ? ?? //取得舊Entry數(shù)組的每個(gè)元素
6? ? ? ?? if (e != null) {
7? ? ? ? ? ?? src[j] = null;//釋放舊Entry數(shù)組的對(duì)象引用(for循環(huán)后,舊的Entry數(shù)組不再引用任何對(duì)象)
8? ? ? ? ? ?? do {
9? ? ? ? ? ? ? ?? Entry next = e.next;
10? ? ? ? ? ? ? ?? int i = indexFor(e.hash, newCapacity); //?。≈匦掠?jì)算每個(gè)元素在數(shù)組中的位置
11? ? ? ? ? ? ? ?? e.next = newTable[i]; //標(biāo)記[1]
12? ? ? ? ? ? ? ?? newTable[i] = e;? ? ? //將元素放在數(shù)組上
13? ? ? ? ? ? ? ?? e = next;? ? ? ? ? ?? //訪(fǎng)問(wèn)下一個(gè)Entry鏈上的元素
14? ? ? ? ? ?? } while (e != null);
15? ? ? ?? }
16? ?? }
17 }
上述方法的擴(kuò)容理念大致就是當(dāng)HashMap中的鍵值對(duì)數(shù)量超過(guò)閾值時(shí)進(jìn)行擴(kuò)容,把容量擴(kuò)展為原來(lái)的2的n次方倍,取得原來(lái)HashMap中每個(gè)鍵值對(duì)并對(duì)每個(gè)鍵值對(duì)進(jìn)行重新計(jì)算數(shù)組分配位置rehash。過(guò)程例子如下:
假設(shè)了我們的hash算法就是簡(jiǎn)單的用key mod 一下表的大?。ㄒ簿褪菙?shù)組的長(zhǎng)度)。其中的哈希桶數(shù)組table的size=2, 所以key = 3、7、5,put順序依次為 5、7、3。在mod 2以后都沖突在table[1]這里了。這里假設(shè)負(fù)載因子 loadFactor=1,即當(dāng)鍵值對(duì)的實(shí)際大小size 大于 table的實(shí)際大小時(shí)進(jìn)行擴(kuò)容。接下來(lái)的三個(gè)步驟是哈希桶數(shù)組 resize成4,然后所有的Node重新rehash的過(guò)程。

上述方法雖然比較完善但還存在不足,為每個(gè)Node重新計(jì)算下標(biāo)是非常耗時(shí)的事情,JDK1.8就對(duì)此做了如下優(yōu)化:
經(jīng)過(guò)觀測(cè)可以發(fā)現(xiàn),我們使用的是2次冪的擴(kuò)展(指長(zhǎng)度擴(kuò)為原來(lái)2倍),所以,元素的位置要么是在原位置,要么是在原位置再移動(dòng)2次冪的位置??聪聢D可以明白這句話(huà)的意思,n為table的長(zhǎng)度,圖(a)表示擴(kuò)容前的key1和key2兩種key確定索引位置的示例,圖(b)表示擴(kuò)容后key1和key2兩種key確定索引位置的示例,其中hash1是key1對(duì)應(yīng)的哈希與高位運(yùn)算結(jié)果。

元素在重新計(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,只需要看看原來(lái)的hash值新增的那個(gè)bit是1還是0就好了,是0的話(huà)索引沒(méi)變,是1的話(huà)索引變成“原索引+oldCap”(原索引+原來(lái)數(shù)組長(zhǎng)度),可以看看下圖為16擴(kuò)充為32的resize示意圖:

這個(gè)設(shè)計(jì)確實(shí)非常的巧妙,既省去了重新計(jì)算hash值的時(shí)間,而且同時(shí),由于新增的1bit是0還是1可以認(rèn)為是隨機(jī)的,因此resize的過(guò)程,均勻的把之前的沖突的節(jié)點(diǎn)分散到新的bucket了。這一塊就是JDK1.8新增的優(yōu)化點(diǎn)。有一點(diǎn)注意區(qū)別,JDK1.7中rehash的時(shí)候,舊鏈表遷移新鏈表的時(shí)候,如果在新表的數(shù)組索引位置相同,則鏈表元素會(huì)倒置,但是從上圖可以看出,JDK1.8不會(huì)倒置。
JDK1.8源碼:
1 final Node[] resize() {
2? ?? Node[] oldTab = table;
3? ?? int oldCap = (oldTab == null) ? 0 : oldTab.length;
4? ?? int oldThr = threshold;
5? ?? int newCap, newThr = 0;
6? ?? if (oldCap > 0) {
7? ? ? ?? // 超過(guò)最大值就不再擴(kuò)充了,就只好隨你碰撞去吧
8? ? ? ?? if (oldCap >= MAXIMUM_CAPACITY) {
9? ? ? ? ? ?? threshold = Integer.MAX_VALUE;
10? ? ? ? ? ?? return oldTab;
11? ? ? ?? }
12? ? ? ?? // 沒(méi)超過(guò)最大值,就擴(kuò)充為原來(lái)的2倍
13? ? ? ?? else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
14? ? ? ? ? ? ? ? ? oldCap >= DEFAULT_INITIAL_CAPACITY)
15? ? ? ? ? ?? newThr = oldThr << 1; // double threshold
16? ?? }
17? ?? else if (oldThr > 0) // initial capacity was placed in threshold
18? ? ? ?? newCap = oldThr;
19? ?? else {? ? ? ? ? ? ?? // zero initial threshold signifies using defaults
20? ? ? ?? newCap = DEFAULT_INITIAL_CAPACITY;
21? ? ? ?? newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
22? ?? }
23? ?? // 計(jì)算新的resize上限
24? ?? if (newThr == 0) {
25
26? ? ? ?? float ft = (float)newCap * loadFactor;
27? ? ? ?? newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
28? ? ? ? ? ? ? ? ?? (int)ft : Integer.MAX_VALUE);
29? ?? }
30? ?? threshold = newThr;
31? ?? @SuppressWarnings({"rawtypes","unchecked"})
32? ? ? ?? Node[] newTab = (Node[])new Node[newCap];
33? ?? table = newTab;
34? ?? if (oldTab != null) {
35? ? ? ?? // 把每個(gè)bucket都移動(dòng)到新的buckets中
36? ? ? ?? for (int j = 0; j < oldCap; ++j) {
37? ? ? ? ? ?? Node e;
38? ? ? ? ? ?? if ((e = oldTab[j]) != null) {
39? ? ? ? ? ? ? ?? oldTab[j] = null;
40? ? ? ? ? ? ? ?? if (e.next == null)
41? ? ? ? ? ? ? ? ? ?? newTab[e.hash & (newCap - 1)] = e;
42? ? ? ? ? ? ? ?? else if (e instanceof TreeNode)
43? ? ? ? ? ? ? ? ? ?? ((TreeNode)e).split(this, newTab, j, oldCap);
44? ? ? ? ? ? ? ?? else { // 鏈表優(yōu)化重hash的代碼塊
45? ? ? ? ? ? ? ? ? ?? Node loHead = null, loTail = null;
46? ? ? ? ? ? ? ? ? ?? Node hiHead = null, hiTail = null;
47? ? ? ? ? ? ? ? ? ?? Node next;
48? ? ? ? ? ? ? ? ? ?? do {
49? ? ? ? ? ? ? ? ? ? ? ?? next = e.next;
50? ? ? ? ? ? ? ? ? ? ? ?? // 原索引
51? ? ? ? ? ? ? ? ? ? ? ?? if ((e.hash & oldCap) == 0) {
52? ? ? ? ? ? ? ? ? ? ? ? ? ?? if (loTail == null)
53? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? loHead = e;
54? ? ? ? ? ? ? ? ? ? ? ? ? ?? else
55? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? loTail.next = e;
56? ? ? ? ? ? ? ? ? ? ? ? ? ?? loTail = e;
57? ? ? ? ? ? ? ? ? ? ? ?? }
58? ? ? ? ? ? ? ? ? ? ? ?? // 原索引+oldCap
59? ? ? ? ? ? ? ? ? ? ? ?? else {
60? ? ? ? ? ? ? ? ? ? ? ? ? ?? if (hiTail == null)
61? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? hiHead = e;
62? ? ? ? ? ? ? ? ? ? ? ? ? ?? else
63? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? hiTail.next = e;
64? ? ? ? ? ? ? ? ? ? ? ? ? ?? hiTail = e;
65? ? ? ? ? ? ? ? ? ? ? ?? }
66? ? ? ? ? ? ? ? ? ?? } while ((e = next) != null);
67? ? ? ? ? ? ? ? ? ?? // 原索引放到bucket里
68? ? ? ? ? ? ? ? ? ?? if (loTail != null) {
69? ? ? ? ? ? ? ? ? ? ? ?? loTail.next = null;
70? ? ? ? ? ? ? ? ? ? ? ?? newTab[j] = loHead;
71? ? ? ? ? ? ? ? ? ?? }
72? ? ? ? ? ? ? ? ? ?? // 原索引+oldCap放到bucket里
73? ? ? ? ? ? ? ? ? ?? if (hiTail != null) {
74? ? ? ? ? ? ? ? ? ? ? ?? hiTail.next = null;
75? ? ? ? ? ? ? ? ? ? ? ?? newTab[j + oldCap] = hiHead;
76? ? ? ? ? ? ? ? ? ?? }
77? ? ? ? ? ? ? ?? }
78? ? ? ? ? ?? }
79? ? ? ?? }
80? ?? }
81? ?? return newTab;
82 }
另外HashMap是線(xiàn)程不安全的,尤其在多線(xiàn)程下進(jìn)行resize擴(kuò)容尤其容易形成環(huán)形鏈表。如果在多線(xiàn)程情況下推薦使用ConcurrentHashMap。