轉自:
https://www.jianshu.com/p/685f074a32be
HashMap遍歷元素的順序。
一,HashMap元素的底層存儲順序
我們都知道HashMap是“無序”的,也就是說不能保證插入順序。但是,HashMap其實也是有序的,一組相同的key-value對,無論他們插入的順序是什么樣的,遍歷時,順序都是一致的。

上面的代碼,分別使用兩種方式插入,也就是說插入順序是不同的,但是遍歷的結果是一樣的。可以這樣理解,只要你插入的是這19個元素,HashMap存儲的時候,都是按照同一個順序來存儲的。
這一點其實也很容易理解,因為HashMap是使用hash算法來定位key的邏輯存儲位置,只要你的key是一樣的,邏輯存儲位置也是一樣的。
遍歷結果如下:
11=11
12=12
13=13
14=14
15=15
16=16
17=17
18=18
19=19
0=0
1=1
2=2
3=3
4=4
5=5
6=6
7=7
8=8
9=9
10=10
為什么是這個順序呢?本人很不理解。
如果說是根據key的hashcode來定位邏輯存儲位置的話,關鍵key的hashcode也不支持這種說法啊。key的hashcode打印如下:
1568
1569
1570
1571
1572
1573
1574
1575
1576
49
50
51
52
53
54
55
56
57
1567
HashMap是通過hash算法把key映射到table數組的某個位置。以這個例子為例來說明,HashMap初始化容量指定為20,最終HashMap會把容量轉化為32,那么這個HashMap的數組是一個長度為32的數組。
(為什么?稍后會說明)
OK,我們有一個長度為32的數組,接下來我們插入第一個元素,也就是key為0的一個字符串,這個key的hashcode值為48。那么這個key會被映射到數組的哪個位置呢?這個key對應數組的下標是多少呢?
這里有一個問題需要注意,長度為32的數組的下標必然是從0到31。然后這個數組的遍歷順序也是從0到31的順序開始遍歷。
查看源碼我才發現,原來,數組的下標並不是key的hashcode值,而是key的hashcode值再經過按位與邏輯運算得到的。
先看一下源碼,其他的代碼我這里就不貼了,我只看最下面這行最關鍵的代碼,如下圖。

newTab[e.hash & (newCap - 1)] = e;
就是這行代碼,這行代碼就是把新增的節點Node放到數組中,放到數組中的哪個位置呢?
放到下標=e.hash & (newCap - 1)的位置!
好,那我就把e.hash & (newCap - 1)打印出來,看看到底是什么?
因為我們有20個元素,HashMap最終擴容以后的大小是32,也就是說:
newCap = 32
e. hash剛才說了,就是key的hashcode值,好,打印看看到底是什么鬼!
0-1-2-3-4-5-6-7-8-16-17-18-19-20-21-22-23-24-25-31
這就是結果!
這就是HashMap元素的存儲順序!
這就是HashMap元素的底層邏輯地址!
這!我的疑惑總算解開了!
下面再說一下2個關鍵問題:
1,指定的size大小是20,容量大小為什么變成了32。
2,HashMap為什么不直接用key的hashcode值來定位數組下標。
二,HashMap擴容的容量大小問題
HashMap初始化時,如果不指定,那么默認的容量大小為16。源代碼中是這樣的:
DEFAULT_INITIAL_CAPACITY = 1 << 4
但是我指定了容量為20,為什么就變成了32呢?
想搞清楚這個問題,必須清楚HashMap容量大小的計算邏輯。
源碼如下:

這個方法是一系列的邏輯位運算。反正我現在是看不懂了,不過沒關系,我把代碼拿過來跑一下看看結果。
當入參是16的時候,返回16。
當入參是20的時候,返回是32。
原來如此啊,HashMap的容量大小必須是2的N次方。
結論:HashMap的容量必須是2的N次方,如果你指定了一個非2的N次方的整數S,那么HashMap在內部會把它轉化為大於S的2的N次方的整數。
當然,這個整數是大於S的2的N次方的整數中最小的那一個。
三,HashMap為什么不直接用key的hashcode值來定位數組下標。
原因其實很簡單,因為本例中HashMap的數組是一個長度為32的數組,下標從0到31。
key的hashcode值超過了31,就不行了。
所以,java的開發者做了一個映射,把key的hashcode值映射到0到31的區間范圍。如何映射的呢?就是上面提到的:
e.hash & (newCap - 1)
把key的hashcode值與31做位與操作。
java的位與操作,忘記的可以自行腦補,我這里不在贅述,網上有很多講解。我這里說一下java設計者的實現思路,為什么要這樣實現。
為什么呢?因為HashMap是可以擴容的,newCap這個變量是動態的,本例中是32,但是如果是64呢,如果是256呢?
這時我就把這個newCap當做一個變量,接受用戶輸入。這樣就成功把key的hashcode值映射到了指定范圍的區間內:
【0到newCap減1】
這里的位與操作是關鍵,我們想把一些隨機的整數映射到指定范圍的區間時,可以考慮使用位與操作。這是計算機邏輯運算的特性,java的開發者們很好的利用了這一特性。
好了,今天的分享到此結束。
作者:鴻雁長飛魚龍潛躍
鏈接:https://www.jianshu.com/p/685f074a32be
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。