最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java面試題之HashMap 的 hash 方法原理是什么

 更新時(shí)間:2021年11月05日 09:11:49   作者:沉默王二  
那天,小二去蔚來面試,面試官老王一上來就問他:HashMap 的 hash 方法的原理是什么?當(dāng)時(shí)就把裸面的小二給蚌埠住了,這篇文章將詳細(xì)解答該題目

Warning:這是《Java 程序員進(jìn)階之路》專欄的第 55 篇。

回來后小二找到了我,于是我就寫下了這篇文章丟給他,并嚴(yán)厲地告訴他:再搞不懂就別來找我。聽到這句話,心頭一陣酸,小二繃不住差點(diǎn)要哭 😭。

PS:本文 GitHub 上已同步,有 GitHub 賬號(hào)的小伙伴,記得看完后給二哥安排一波 star 呀!沖一波 GitHub 的 trending 榜單,求求各位了。

GitHub 地址:https://github.com/itwanger/toBeBetterJavaer

來看一下 hash 方法的源碼(JDK 8 中的 HashMap):

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

這段代碼究竟是用來干嘛的呢?

我們都知道,key.hashCode() 是用來獲取鍵位的哈希值的,理論上,哈希值是一個(gè) int 類型,范圍從-2147483648 到 2147483648。前后加起來大概 40 億的映射空間,只要哈希值映射得比較均勻松散,一般是不會(huì)出現(xiàn)哈希碰撞的。

但問題是一個(gè) 40 億長(zhǎng)度的數(shù)組,內(nèi)存是放不下的。HashMap 擴(kuò)容之前的數(shù)組初始大小只有 16,所以這個(gè)哈希值是不能直接拿來用的,用之前要和數(shù)組的長(zhǎng)度做取模運(yùn)算,用得到的余數(shù)來訪問數(shù)組下標(biāo)才行。

取模運(yùn)算有兩處。

取模運(yùn)算(“Modulo Operation”)和取余運(yùn)算(“Remainder Operation ”)兩個(gè)概念有重疊的部分但又不完全一致。主要的區(qū)別在于對(duì)負(fù)整數(shù)進(jìn)行除法運(yùn)算時(shí)操作不同。取模主要是用于計(jì)算機(jī)術(shù)語(yǔ)中,取余則更多是數(shù)學(xué)概念。

一處是往 HashMap 中 put 的時(shí)候(putVal 方法中):

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
     HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
     if ((tab = table) == null || (n = tab.length) == 0)
         n = (tab = resize()).length;
     if ((p = tab[i = (n - 1) & hash]) == null)
         tab[i] = newNode(hash, key, value, null);
}

一處是從 HashMap 中 get 的時(shí)候(getNode 方法中):

final Node<K,V> getNode(int hash, Object key) {
     Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
     if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {}
}

其中的 (n - 1) & hash 正是取模運(yùn)算,就是把哈希值和(數(shù)組長(zhǎng)度-1)做了一個(gè)“與”運(yùn)算。

可能大家在疑惑:取模運(yùn)算難道不該用 % 嗎?為什么要用 & 呢?

這是因?yàn)?& 運(yùn)算比 % 更加高效,并且當(dāng) b 為 2 的 n 次方時(shí),存在下面這樣一個(gè)公式。

a % b = a & (b-1)

用 2 n 2^n 2n 替換下 b 就是:

image.png

我們來驗(yàn)證一下,假如 a = 14,b = 8,也就是 2 3 2^3 23,n=3。

14%8,14 的二進(jìn)制為 1110,8 的二進(jìn)制 1000,8-1 = 7 的二進(jìn)制為 0111,1110&0111=0110,也就是 0* 2 0 2^0 20+1* 2 1 2^1 21+1* 2 2 2^2 22+0* 2 3 2^3 23=0+2+4+0=6,14%8 剛好也等于 6。

這也正好解釋了為什么 HashMap 的數(shù)組長(zhǎng)度要取 2 的整次方。

因?yàn)椋〝?shù)組長(zhǎng)度-1)正好相當(dāng)于一個(gè)“低位掩碼”——這個(gè)掩碼的低位最好全是 1,這樣 & 操作才有意義,否則結(jié)果就肯定是 0,那么 & 操作就沒有意義了。

a&b 操作的結(jié)果是:a、b 中對(duì)應(yīng)位同時(shí)為 1,則對(duì)應(yīng)結(jié)果位為 1,否則為 0

2 的整次冪剛好是偶數(shù),偶數(shù)-1 是奇數(shù),奇數(shù)的二進(jìn)制最后一位是 1,保證了 hash &(length-1) 的最后一位可能為 0,也可能為 1(這取決于 h 的值),即 & 運(yùn)算后的結(jié)果可能為偶數(shù),也可能為奇數(shù),這樣便可以保證哈希值的均勻性。

& 操作的結(jié)果就是將哈希值的高位全部歸零,只保留低位值,用來做數(shù)組下標(biāo)訪問。

假設(shè)某哈希值為 10100101 11000100 00100101,用它來做取模運(yùn)算,我們來看一下結(jié)果。HashMap 的初始長(zhǎng)度為 16(內(nèi)部是數(shù)組),16-1=15,二進(jìn)制是 00000000 00000000 00001111(高位用 0 來補(bǔ)齊):

10100101 11000100 00100101
& 00000000 00000000 00001111
----------------------------------
00000000 00000000 00000101

因?yàn)?15 的高位全部是 0,所以 & 運(yùn)算后的高位結(jié)果肯定是 0,只剩下 4 個(gè)低位 0101,也就是十進(jìn)制的 5,也就是將哈希值為 10100101 11000100 00100101 的鍵放在數(shù)組的第 5 位。

明白了取模運(yùn)算后,我們?cè)賮砜?put 方法的源碼:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

以及 get 方法的源碼:

public V get(Object key) {
    HashMap.Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

它們?cè)谡{(diào)用 putVal 和 getNode 之前,都會(huì)先調(diào)用 hash 方法:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

那為什么取模運(yùn)算之前要調(diào)用 hash 方法呢?

看下面這個(gè)圖。

某哈希值為 11111111 11111111 11110000 1110 1010,將它右移 16 位(h >>> 16),剛好是 00000000 00000000 11111111 11111111,再進(jìn)行異或操作(h ^ (h >>> 16)),結(jié)果是 11111111 11111111 00001111 00010101

異或(^)運(yùn)算是基于二進(jìn)制的位運(yùn)算,采用符號(hào) XOR 或者^來表示,運(yùn)算規(guī)則是:如果是同值取 0、異值取 1

由于混合了原來哈希值的高位和低位,所以低位的隨機(jī)性加大了(摻雜了部分高位的特征,高位的信息也得到了保留)。

結(jié)果再與數(shù)組長(zhǎng)度-1(00000000 00000000 00000000 00001111)做取模運(yùn)算,得到的下標(biāo)就是 00000000 00000000 00000000 00000101,也就是 5。

還記得之前我們假設(shè)的某哈希值 10100101 11000100 00100101 嗎?在沒有調(diào)用 hash 方法之前,與 15 做取模運(yùn)算后的結(jié)果也是 5,我們不妨來看看調(diào)用 hash 之后的取模運(yùn)算結(jié)果是多少。

某哈希值 00000000 10100101 11000100 00100101(補(bǔ)齊 32 位),將它右移 16 位(h >>> 16),剛好是 00000000 00000000 00000000 10100101,再進(jìn)行異或操作(h ^ (h >>> 16)),結(jié)果是 00000000 10100101 00111011 10000000

結(jié)果再與數(shù)組長(zhǎng)度-1(00000000 00000000 00000000 00001111)做取模運(yùn)算,得到的下標(biāo)就是 00000000 00000000 00000000 00000000,也就是 0。

綜上所述,hash 方法是用來做哈希值優(yōu)化的,把哈希值右移 16 位,也就正好是自己長(zhǎng)度的一半,之后與原哈希值做異或運(yùn)算,這樣就混合了原哈希值中的高位和低位,增大了隨機(jī)性。

說白了,hash 方法就是為了增加隨機(jī)性,讓數(shù)據(jù)元素更加均衡的分布,減少碰撞。

到此這篇關(guān)于Java面試題之HashMap 的 hash 方法原理是什么的文章就介紹到這了,更多相關(guān)Java HashMap內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java網(wǎng)絡(luò)編程之UDP網(wǎng)絡(luò)通信詳解

    Java網(wǎng)絡(luò)編程之UDP網(wǎng)絡(luò)通信詳解

    這篇文章主要為大家詳細(xì)介紹了Java網(wǎng)絡(luò)編程中的UDP網(wǎng)絡(luò)通信的原理與實(shí)現(xiàn),文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下
    2022-09-09
  • 解決java錯(cuò)誤:不支持發(fā)行版本5

    解決java錯(cuò)誤:不支持發(fā)行版本5

    這篇文章主要給大家介紹了關(guān)于如何解決java錯(cuò)誤:不支持發(fā)行版本5的相關(guān)資料,發(fā)行版本5是Java5,已經(jīng)是十多年前的版本了,現(xiàn)在已經(jīng)不再被支持,需要的朋友可以參考下
    2023-07-07
  • 全面解析@InsertProvider執(zhí)行原理

    全面解析@InsertProvider執(zhí)行原理

    這篇文章主要介紹了全面解析@InsertProvider執(zhí)行原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • SpringBoot啟動(dòng)執(zhí)行sql腳本的3種方法實(shí)例

    SpringBoot啟動(dòng)執(zhí)行sql腳本的3種方法實(shí)例

    在應(yīng)用程序啟動(dòng)后,可以自動(dòng)執(zhí)行建庫(kù)、建表等SQL腳本,下面這篇文章主要給大家介紹了關(guān)于SpringBoot啟動(dòng)執(zhí)行sql腳本的3種方法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-01-01
  • 基于spring+springmvc+hibernate 整合深入剖析

    基于spring+springmvc+hibernate 整合深入剖析

    這篇文章主要介紹了于spring+springmvc+hibernate整合實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Kafka 網(wǎng)絡(luò)中斷和網(wǎng)絡(luò)分區(qū)4種場(chǎng)景分析

    Kafka 網(wǎng)絡(luò)中斷和網(wǎng)絡(luò)分區(qū)4種場(chǎng)景分析

    這篇文章主要介紹了Kafka 網(wǎng)絡(luò)中斷和網(wǎng)絡(luò)分區(qū)4種場(chǎng)景分析
    2007-02-02
  • 使用Java操作Parquet文件的基本步驟

    使用Java操作Parquet文件的基本步驟

    Parquet 是一個(gè)強(qiáng)大的列式存儲(chǔ)格式,適用于大數(shù)據(jù)場(chǎng)景,能夠高效地進(jìn)行數(shù)據(jù)壓縮、查詢和存儲(chǔ),在 Java 中使用 Apache Spark 讀取和寫入 Parquet 文件是一項(xiàng)常見的任務(wù),本文給大家介紹了在 Java 中使用 Spark 來讀取和寫入 Parquet 文件的基本步驟,需要的朋友可以參考下
    2025-03-03
  • springboot配置ldaps連接方式

    springboot配置ldaps連接方式

    這篇文章主要介紹了springboot配置ldaps連接方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • SpringBoot使用自動(dòng)配置xxxAutoConfiguration

    SpringBoot使用自動(dòng)配置xxxAutoConfiguration

    這篇文章介紹了SpringBoot自動(dòng)配置xxxAutoConfiguration的使用方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-12-12
  • MyBatis使用Map與模糊查詢的方法示例

    MyBatis使用Map與模糊查詢的方法示例

    這篇文章主要給大家介紹了關(guān)于MyBatis使用Map與模糊查詢的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05

最新評(píng)論

剑河县| 迁安市| 浮梁县| 沁水县| 贵定县| 灵璧县| 株洲市| 河东区| 林口县| 连云港市| 钦州市| 卢湾区| 连城县| 台州市| 永靖县| 思茅市| 剑阁县| 土默特左旗| 古浪县| 阿巴嘎旗| 双城市| 厦门市| 久治县| 和林格尔县| 施甸县| 同德县| 凤翔县| 贡嘎县| 宜都市| 桃江县| 麻江县| 谷城县| 旅游| 汝城县| 富平县| 乡宁县| 资兴市| 开平市| 潼南县| 广南县| 普陀区|