日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

Java集合之HashMap源码分析

發(fā)布時間:2024/8/23 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java集合之HashMap源码分析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

以下源碼均為jdk1.7

HashMap概述

HashMap是基于哈希表的Map接口的非同步實(shí)現(xiàn). 提供所有可選的映射操作, 并允許使用null值和null健. 此類不保證映射的順序.

需要注意的是: HashMap不是同步的.

哈希表

哈希表定義: 哈希表是一種根據(jù)關(guān)鍵碼去尋找值的數(shù)據(jù)映射結(jié)構(gòu), 該結(jié)構(gòu)通過把關(guān)鍵碼映射的位置去尋找存放值的地方.

舉個例子, 最典型的例子就是字典, 如果想要在字典中查找"按"字, 通常會根據(jù)拼音 an 去查找拼音索引(當(dāng)然也可以是偏旁索引), 然后找到 ti 在字典中的位置, 得到第一個拼音為 an 的字 "安". 這個過程就是鍵碼映射, 即 通過 key 查找 f(key). 其中 key為關(guān)鍵字, f()是哈希函數(shù), 哈希函數(shù)的結(jié)果就是哈希值.

哈希沖突:?那么問題來了, 我們要查找的是"按",而不是"安", 但他們的拼音都是一樣的. 通過關(guān)鍵字 an "按"和"安"可以映射到一樣的字典頁碼4的位置, 這就是哈希沖突(也叫哈希碰撞), 在公式上表達(dá)就是 key1 != key2, 但f(key1)=f(key2).

key 為值, f(key)計(jì)算得出數(shù)組中存儲地址, 這樣就會出現(xiàn)兩個元素的地址相同的情況. 這時, 哈希函數(shù)的設(shè)計(jì)就至關(guān)重要了, 好的哈希函數(shù)會盡可能的保證 計(jì)算簡單和散列地址分布均勻, 但是, 數(shù)組是一個連續(xù)的固定長度的內(nèi)存空間, 再好的哈希函數(shù)也不能保證得到的存儲地址絕不發(fā)生沖突.

哈希沖突的解決方案有多種: 開放定址法(發(fā)生沖突, 尋找下一個), 再散列函數(shù)法, 鏈地址法.

HashMap就是采用了鏈地址發(fā), 也就是 數(shù)組+鏈表 的方式.

HashMap的實(shí)現(xiàn)原理

最基本的數(shù)據(jù)結(jié)構(gòu)有兩種: 數(shù)組和指針, HashMap就是通過這兩個數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的, 是數(shù)組和鏈表的結(jié)合體.

?

從圖中可以看出, HashMap底層是一個數(shù)組結(jié)構(gòu), 數(shù)組中的每一項(xiàng)是一個鏈表. 當(dāng)新建HashMap時, 會初始化一個數(shù)組.

HashMap的主干是一個Entry數(shù)組.

?

Entry是一個靜態(tài)內(nèi)部類, 包含 key-value.

?

HashMap存儲的整體結(jié)構(gòu)如下:

?

簡單說, HashMap有數(shù)組+鏈表組成, 數(shù)組是HashMap的主體, 鏈表是為了解決哈希沖突而存在的, 如果定位到數(shù)組位置不含鏈表(當(dāng)前entry的next指向null), 那么對于查找,添加等操作很快, 僅需一次尋址即可; 如果定位到的數(shù)組包含鏈表, 那么添加操作就要遍歷鏈表, 然后通過key的equals方法進(jìn)行逐一對比, 存在即覆蓋, 不存在則新增, 而查找操作也需遍歷鏈表.

所以, 性能考慮, HashMap中的鏈表出現(xiàn)越少, 性能越好.

HasmMap幾個重要的字段:

?

?

?

?

?

HashMap的構(gòu)造函數(shù):

?

從上面代碼中可以看出, 在常規(guī)構(gòu)造器中, 沒有為數(shù)組 table 分配內(nèi)存空間(有個參數(shù)為map的構(gòu)造器除外), 而是在執(zhí)行 put操作時才真正構(gòu)建table數(shù)組

?

再來看 inflateTable()方法源碼:

?

重量級角色, 哈希函數(shù)出場:

?

indexFor()函數(shù)實(shí)現(xiàn)如下:

?

h&(length - 1)保證獲取的index一定在數(shù)組的范圍內(nèi), 例如: 容量為16, length-1=15, h=18, 進(jìn)行計(jì)算為:

?

得出index=2.

故而, 最終存儲位置的確定為如下流程:

?

最后看下 addEntry 的實(shí)現(xiàn):

?

通過 addEntry 的代碼可以看出, 當(dāng)發(fā)生哈希沖突并且size大于閾值時, 需要進(jìn)行數(shù)組擴(kuò)容, 擴(kuò)容時, 需要新建一個長度為之前2倍的新數(shù)組, 最后將當(dāng)前的Entry數(shù)組中元素全部傳過去, 擴(kuò)容后的新數(shù)組長度為之前的2倍, 所以擴(kuò)容相對來說是一個耗資源的操作.

下面看get方法就簡單得多了:

?

然后是getEntry()源碼:

?

可以看出, get方法的實(shí)現(xiàn)相當(dāng)簡單, 流程為: key(hashcode)-->hash-->indexFor-->最終索引位置, 找到對應(yīng)位置table[i], 在查看是否有鏈表, 遍歷鏈表, 通過key的equals方法比對查找對應(yīng)的記錄.

在getEntry方法中, 定位到數(shù)組位置之后遍歷鏈表的時候, e.hash==hash這個判斷是否有必要. 試想如下場景, 如果傳入的key對象重寫了equals方法卻沒有重寫hashCode, 而恰巧此對象定位到這個數(shù)組位置, 如果僅僅用equals判斷可能是相等的, 但其hashCode和當(dāng)前對象不一致, 這種情況, 根據(jù)Object的hashCode的約定, 不能返回當(dāng)前對象, 而應(yīng)該返回null.

重寫equals方法要同時重寫hashCode方法

為什么重寫equals時也要同時重寫hashCode? 下面舉個小例子:

?

實(shí)際輸出結(jié)果:

結(jié)果: null

現(xiàn)在我們已經(jīng)對HashMap的原理有了一定了解, 這個結(jié)果就不難理解了. 盡管我們在進(jìn)行g(shù)et和put操作的時候, 使用的key從邏輯上講是等值的, 但由于沒有重寫hashCode方法, 在進(jìn)行put操作時: key(hashcod1)-->hash-->indexFor-->最終索引位置; 而通過key去除value時: key(hashcode2)-->hash-->indexFor-->最終索引位置, 由于hashcode1和hashcode2不相等, 最終得出的數(shù)組索引頁不一樣而返回null(也可能碰巧定位到了一個數(shù)組位置, 但是也會判斷其entry的hash值是否相等, 上面get方法中有提到)

所以, 在重寫equals方法時, 必須注意重寫hashCode方法, 同時還要保證equals判斷相等的兩個對象, 調(diào)用hashCode方法要返回同樣的整數(shù)值, 而equals判斷不相等的兩個對象, 其hashCode可以相同, 只是會發(fā)生哈希沖突, 應(yīng)該盡量避免.

HashMap的遍歷

?

總結(jié)

HashMap底層將key-value當(dāng)成一個整體處理, 這個整體就是Entry對象. HashMap底層采用一個Entry[]數(shù)組來保存所有的key-value對, 當(dāng)需要存儲一個Entry對象時, 會根據(jù)hash算法來決定其在數(shù)組中的位置, 再根據(jù)equals方法決定其在該數(shù)組位置上的鏈表中的存儲位置; 當(dāng)需要取出一個Entry時, 也會根據(jù)hash算法找到其在數(shù)組中的存儲位置, 再根據(jù)equals方法從該位置上的鏈表中取出該Entry.

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎

總結(jié)

以上是生活随笔為你收集整理的Java集合之HashMap源码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。