天天看點

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

在java集合中,HashMap是用來存放一組鍵值對的數,也就是key-value形式的資料,而在jdk1.6和jdk1.8的實作有所不同。

​JDK1.6的源碼實作:​

首先來看一下HashMap的類的定義:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

HashMap繼承了AbstractHashMap,實作了Map,Cloneable和Serializable接口,Map定義了一些公共的接口,而AbstractHashMap也實作了Map接口,并提供了一些預設的實作,如size方法和isEmpty方法等

在HashMap中,定義了幾個常量:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

初始容量,如果在建立HashMap的時候沒有指定容量,就使用初始容量

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

最大容量,HashMap中存儲元素的數組的最大的容量,為2的30次方

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

預設的加載因子,在擴容的時候使用

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

最重要的一個常量,用來存放我們添加的元素,

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

門檻值,用來判斷HashMap是否需要擴容,如果添加的元素超過該值,則需要擴容, 該值等于 capacity * loadFactor,比如 預設的初始容量為16, 預設的加載因子為0.75,則門檻值就等于16*0.75=12,在table數組中,如果數組的元素個數超過12,則table數組就需要進行擴容。

HashMap提供了三個構造方法,我們可以指定初始容量和加載因子來構造HashMap

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

也可以隻指定初始容量來構造HashMap

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

也可以都不指定,這時,初始容量和加載因子都是用的預設的值,一般情況下也不會去指定初始容量和加載因子。

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

如果采用不帶參數的構造方法,可以看到存放元素的初始數組的大小為16,門檻值為12。

相當于 Entry[] table = new Entry[16],在HashMap内部使用 Entry數組來存放元素的。

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

可以看到Entry表示的是一個單向連結清單的結構,next就是指向下一個節點;也就是說在HashMap内部,使用數組+連結清單的形式來存放元素,數組的每一項就是一個連結清單。HashMap的結構圖大緻如下所示:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

接下來看一下對HashMap的常用操作:

1. put(key, value)操作,向HashMap中添加元素

1)添加的時候,首先要計算key的hash值,找到對應數組的下标

2)找到該下标對應的數組位置的連結清單,周遊連結清單,把值添加到該連結清單上

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

addEntry()方法如下:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

用圖來說明:

1. 初始化一個空的HashMap,此時還沒有元素,結構如下:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

假設要添加一對資料:key="zs", value="zhangsan"

首先對 key進行hash,比如 hash之後的值為5,之後在用hash和table.length來求數組的索引,

比如索引 i = 4,此時,這對元素就應該在 table[i] 即 table[4] 的位置處,取得該處的Entry連結清單,此時,連結清單為空,建立一個Entry節點,加入到該空連結清單中:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

此時,在添加一對元素:key="ls", value="lisi",假如計算的索引 i 恰好等于4,此時,取得 table[4] 處的連結清單  Entry<K, V> = table[4], 用key = "ls"在這個連結清單上進行周遊,看看是否該key已存在:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

此時,key="ls"并不存在,又會建立一個Entry節點,加入到該清單中:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

如果此時,又添加 key="zs", value="zhangsan222",根據key計算到的索引為4,取出 table[4]處的連結清單,周遊連結清單,然後檢查對應的key是否存在,檢查到key已經存在了,是以會把新的值替換舊的值即可,不用建立新的節點。

2.get(key)操作

1)根據key計算hash值,根據hash值和數組長度計算數組的下标索引

2)取得該下标對應的連結清單,周遊連結清單,找到key對應的value

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

3.remove()操作,

    1)根據key計算hash值,根據hash值和數組長度計算數組的下标索引

    2)取得該下标對應的連結清單,周遊連結清單,删除key對應的Entry節點

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

removeEntryForKey()方法如下:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

假設現在HashMap中元素的分布如下:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

要删除 key="ls"的元素,假如計算的索引 i=4,要去周遊 table[4]處的連結清單,删除對應的節點,key="ls"為連結清單的第一個節點:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

以上就是jdk1.6中HashMap的實作,是基于數組+連結清單的形式來存放資料的。

​JDK1.8的源碼實作:​

在JDK1.8中,HashMap的實作比1.6的實作要複雜得多,1.8中引入了紅黑樹的資料結構;

除了上面列出來的常量外,新增加了幾個常量:

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

表示的是,如果數組中連結清單的元素大于該值,則需要把該連結清單轉化為紅黑樹,

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

如果連結清單中的元素個數小于該值,則把紅黑樹轉換為連結清單

在JDK1.6中,使用一個Entry數組來存放元素,而在JDK1.8中,使用的Node數組和TreeNode來存放元素,

Node:其實,Node和Entry沒有什麼差別,

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】

TreeNode:表示的是一個紅黑樹結構

HashMap源碼分析-jdk1.6和jdk1.8的差別【面試+工作】