HashMap是無序的


轉自:

https://www.jianshu.com/p/685f074a32be

HashMap遍歷元素的順序。

 

一,HashMap元素的底層存儲順序

 

我們都知道HashMap是“無序”的,也就是說不能保證插入順序。但是,HashMap其實也是有序的,一組相同的key-value對,無論他們插入的順序是什么樣的,遍歷時,順序都是一致的。

 

 
圖片發自簡書App

上面的代碼,分別使用兩種方式插入,也就是說插入順序是不同的,但是遍歷的結果是一樣的。可以這樣理解,只要你插入的是這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值再經過按位與邏輯運算得到的。

 

先看一下源碼,其他的代碼我這里就不貼了,我只看最下面這行最關鍵的代碼,如下圖。

 

 

 
圖片發自簡書App

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容量大小的計算邏輯。

 

源碼如下:

 

 
圖片發自簡書App

這個方法是一系列的邏輯位運算。反正我現在是看不懂了,不過沒關系,我把代碼拿過來跑一下看看結果。

當入參是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
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM