9.5-全棧Java筆記:Map接口中的實現類HashMap

上節聊到「Map接口和實現類」,今天我們深入探討其實現類中的HashMap如何進行底層實現。

Hashmap基本結構講解

哈希表的基本結構就是“數組+鏈表”。我們打開HashMap源碼,發現有如下兩個核心內容:

public?class?? HashMap<K,V>

????extends?? AbstractMap<K,V>

????implements?? Map<K,V>, Cloneable, Serializable {

????/**

???? * The default initial capacity?-?? MUST be a power of two.

???? ? *?核心數組默認初始化的大小為16(數組大小必須為2的整數冪)

???? */

????static?final?int?DEFAULT_INITIAL_CAPACITY?= ? 16;

????/**

???? * The load factor used when none ? specified in constructor.

?? ??*??負載因子(核心數組被占用超過0.75,則開始啟動擴容)

???? */

????static?final?float?DEFAULT_LOAD_FACTOR?= ? 0.75f;

????/**

???? * The table, resized as necessary. ? Length MUST Always be a power of two.?? ??核心數組(根據需要可以擴容)。數組長度必須始終為2的整數冪。

???? */

????transient?? Entry[]?table;

????/**

???? * The number of key-value ? mappings contained in this map.

???? *? ??存儲的鍵值對的數量

???? */

????transient?int?size;

????/**

???? * The next size value at which to resize ? (capacity * load factor).

???? *??擴容后新數組的大小???? ?

???? */

????int?threshold;

其中的,Entry[] table?就是HashMap的核心數組結構,我們也稱之為“位桶數組”。我們再繼續看Entry是什么,源碼如下:

????static?class?Entry<K,V>?implements?? Map.Entry<K,V> {

????????final?K?key;

??????? V?value;

????????Entry<K,V>?next;

????????final?int?hash;

????????/**

???????? * Creates new entry.

??????? ?*/

??????? Entry(int?h, ? K k, V v,?Entry<K,V> ? n) {

????????????value?= ? v;

????????????next?= ? n;

????????????key?= ? k;

????????????hash?= ? h;

??????? }

??? //其余代碼省略

}

一個Entry對象存儲了:

key:鍵對象??????????????

value:值對象

next:下一個節點

hash:?鍵對象的hash值?

顯然就是一個單向鏈表結構,我們使用圖形表示一個Entry的典型示意:

然后,我們畫出Entry[]數組的結構(這也是HashMap的結構):

存儲數據過程put(key,value)

明白了HashMap的基本結構后,我們繼續深入學習HashMap如何存儲數據。此處的核心是如何產生hash值,該值用來對應數組的存儲位置。

我們的目的是將”key-value兩個對象”成對存放到HashMap的Entry[]數組中。

第一步:獲得key對象的hashcode

首先調用key對象的hashcode()方法,獲得hashcode。

第二步:根據hashcode計算出hash值(要求在[0, 數組長度-1]區間)

hashcode是一個整數,我們需要將它轉化成范圍在[0, 數組長度-1]的范圍。我們要求轉化后的hash值盡量均勻的分布在[0,數組長度-1]這個區間,減少“hash沖突”。?


一種極端簡單和低下的算法是:

hash值 = hashcode/hashcode; ?

也就是說,hash值總是1。意味著,鍵值對對象都會存儲到數組索引1位置,這樣就形成一個非常長的鏈表。相當于每存儲一個對象都會發生“hash沖突”,HashMap也退化成了一個“鏈表”。


一種簡單和常用的算法是(相除取余算法):

ash值 =? hashcode%數組長度

這種算法可以讓hash值均勻的分布在[0,數組長度-1]的區間。 早期的HashTable就是采用這種算法。但是,這種算法由于使用了“除法”,效率低下。JDK后來改進了算法。


首先約定數組長度必須為2的整數冪,這樣采用位運算即可實現取余的效果:

hash值 = hashcode&(數組長度-1)

如下為我們自己測試簡單的hash算法

public?class?? Test {

????public?static?void?? main(String[] args) {

???????int?h ? = 25860399;

???????int?? length = 16;?????//length為2的整數次冪,則h&(length-1)就相當于對length取模

???????myHash(h, length);

??? }

????/**

??? ?*

??? ?*?@param?? h??任意整數

??? ?*?@param?? length??長度必須為2的整數冪

??? ?*?@return

??? ?*/

????public?static??int?? myHash(int?h,int?? length){

?????? System.out.println(h&(length-1));

????//length為2的整數冪情況下,和取余的值一樣

?????? System.out.println(h%length);??????//取余數

???????return?? h&(length-1);

??? }

}

運行如上程序,我們就能發現直接取余(h%length)和位運算(h&(length-1))結果是一致的。

事實上,為了獲得更好的散列效果,JDK對hashcode進行了兩次散列處理(核心目標就是為了分布更散更均勻),源碼如下:

static?int?? hash(int?h) {

????// This function ensures ? that hashCodes that differ only by

????// constant multiples at ? each bit position have a bounded

????// number of collisions ? (approximately 8 at default load factor).

??? h ^= (h >>> 20) ^ (h >>> ? 12);

????return?h ? ^ (h >>> 7) ^ (h >>> 4);

}

static?int?? indexFor(int?h,?int?? length) {

????return?h ? & (length-1);

}

第三步:生成Entry對象

一個Entry對象包含4部分:key對象、value對象、hash值、下一個Entry對象。我們現在算出了hash值。下一個Entry對象為null等。

第四步:將Entry對象放到table數組中

如果本Entry對象對應的數組索引位置還沒有放Entry對象,則直接將Entry對象存儲進數組。

如果對應索引位置已經有Entry對象,則將已有Entry對象的next指向本Entry對象,形成鏈表。

總結如上過程:

當添加一個元素(key-value)時,首先計算key的hash值,以此確定插入數組中的位置,但是可能存在同一hash值的元素已經被放在數組同一位置了,這時就添加到同一hash值的元素的后面,他們在數組的同一位置,就形成了鏈表,同一個鏈表上的Hash值是相同的,所以說數組存放的是鏈表。 JDK8中,當鏈表長度大于8時,鏈表就轉換為紅黑樹,這樣又大大提高了查找的效率。

取數據過程get(key)

我們需要通過key對象獲得“鍵值對”對象,進而返回value對象。明白了存儲數據過程,取數據就比較簡單了。

第一步:獲得key的hashcode,通過hash()散列算法得到hash值,進而定位到數組的位置。

第二步:在鏈表上挨個比較key對象。 調用equals()方法,將key對象和鏈表上所有節點的key對象進行比較,直到碰到返回true的節點對象為止。

第三步:返回equals()為true的節點對象的value對象。

明白了存取數據的過程,我們再來看一下hashcode()和equals方法的關系:

? Java中規定,兩個內容相同(equals()為true)的對象必須具有相等的 hashCode

如果equals()為true,兩個對象的hashcode不同;那在整個存儲過程中就發生了悖論。?

擴容問題

HashMap的位桶數組,初始大小為16。實際使用時,顯然大小是可變。如果位桶數組中的元素達到(0.75*數組 length), 就重新調整數組大小變為原來2倍大小。

擴容很耗時。擴容本質就是定義新的更大的數組,并將舊數組內容挨個拷貝到新數組中。

JDK8將鏈表在大于8情況下變為紅黑二叉樹

JDK8中,HashMap在存儲一個元素時,當對應鏈表長度大于8時,鏈表就轉換為紅黑樹,這樣又大大提高了查找的效率。

下一節,我們簡單介紹一個二叉樹。同時,也便于大家理解TreeMap的底層結構。



「全棧Java筆記」是一部能幫大家從零到一成長為全棧Java工程師系列筆記。筆者江湖人稱 Mr. G,10年Java研發經驗,曾在神州數碼、航天院某所研發中心從事軟件設計及研發工作,從小白逐漸做到工程師、高級工程師、架構師。精通Java平臺軟件開發,精通JAVAEE,熟悉各種流行開發框架。


? 筆記包含從淺入深的六大部分:

? A-Java入門階段

? B-數據庫從入門到精通

? C-手刃移動前端和Web前端

? D-J2EE從了解到實戰

? E-Java高級框架精解

? F-Linux和Hadoop?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容