HashMap原理

HashMap基本原理

HashMap原理圖

HashMap主體上是由一個數(shù)組來存儲數(shù)據(jù),每一個索引的位置在這里我們可以先管他叫“桶”,假設(shè)你定一個HashMap的長度為16,那么就可以說是由16個桶構(gòu)成的數(shù)組。當(dāng)我們插入一個數(shù)據(jù)的時候,并不是按順序一個一個向后存儲的,在HashMap中專門定義了一套用于定位數(shù)據(jù)存儲位置的哈希散列算法來確定數(shù)據(jù)具體的存儲位置,因為是哈希散列計算,所以會存在哈希散列碰撞,意思就是相同的Key計算出的哈希值是相同的,在HashMap中使用的是拉鏈法來解決,散列碰撞問題。

當(dāng)兩個Key的計算出的Hash值相同,但是key的值不同,那么他們就會被定位到同一個HashMap的桶中,這個時候HashMap為了不讓他們相互覆蓋,就會在同一個桶中構(gòu)建一個鏈表來存儲他們。

但是隨著HashMap中存儲的數(shù)據(jù)越來越多,發(fā)生碰撞的概率就會越大,某個桶中的鏈表就會越來越長,當(dāng)一個桶中的鏈表長度達到8這個閾值的時候,HashMap就將桶中的鏈表轉(zhuǎn)換成紅黑樹,以達到提高性能的目的。

HashMap中的桶的長度并不會一成不變,而是當(dāng)HashMap中存儲元素的數(shù)量達到總?cè)萘康?5%的時候就會,觸發(fā)HashMap的擴容機制,這個時候HashMap就會對桶大小進行擴容,擴容的數(shù)量為當(dāng)前桶大小的兩倍。以達到提高性能的目的。

默認(rèn)容量

HashMap的容量就是數(shù)組的長度,默認(rèn)容量是16

  • 初始化時如果沒有指定容量,則會使用默認(rèn)值16,我們也可以為HashMap設(shè)置初始容量,而HashMap要求初始容量必須是2的冪次方。保證容量為2的冪次方的目的是:保證最后散列計算的結(jié)果是均勻
  • 在設(shè)置HashMap初始化容量時HashMap會對傳入的初始容量值進行轉(zhuǎn)換,并保證初始容量都能為2的冪次方(通過tableSizeFor來保證)

HashMap如何確定Key的唯一性

HashMap是不允許存在相同Key的。
key的唯一性同時滿足如下兩個條件:

  • 比較Key的Hash值是否相同。
  • 比較是否同一個對象或者key的值是否相等。
    以上兩個條件都為true,則可認(rèn)為是同一個Key。

HashMap為什么要求容量必須是2的冪次方

HashMap容量必須是 2 的冪次方,這樣設(shè)計是為了保證散列計算更加均勻,計算索引的公式為 index = (n - 1) & hash

HashMap的擴容條件

  • HashMap中的鍵值對數(shù)據(jù)達到擴容閾值的時候就會觸發(fā)擴容,擴容閾值=容量*負載因子,負載因子默認(rèn)值為0.75。
  • HashMap擴容的時候,會創(chuàng)建一個容量為原來容量2倍的新數(shù)組,將數(shù)據(jù)從舊的數(shù)組中取出存入新的數(shù)組中

HashMap擴容負載因為為什么默認(rèn)值設(shè)置為0.75

默認(rèn)值是0.75是對HashMap查詢插入時間和空間利用率權(quán)衡得出的結(jié)果,當(dāng)負載因子為0.75的時候,避免了相當(dāng)多的Hash沖突,HashMap底層的鏈表和紅黑樹的高度也比較低,在HashMap的空間利用率、插入和查詢效率都比較高。

  • 如果設(shè)置負載因子值為1.0,就意味著當(dāng)HashMap中的數(shù)組填滿之后才會觸發(fā)擴容,雖然空見的利用率達到了最高。但是也會因為Hash沖突增加,會導(dǎo)致底層的紅黑樹的高度增高,從而影響數(shù)據(jù)的插入和查詢性能。
  • 如果設(shè)置負載因子值為0.5,就意味著當(dāng)HahMap存儲的數(shù)據(jù)達到容量的一半時就會觸發(fā)擴容,雖然這樣Hash沖突減少和查詢效率會提升,但是在空間的利用率上就降低了,很多空間還沒來得及使用就觸發(fā)擴容了。

鏈表紅黑樹如何互相轉(zhuǎn)換,閾值多少?

  • 鏈表轉(zhuǎn)紅黑樹的閾值為:8
  • 紅黑樹轉(zhuǎn)換成鏈表的閾值為:6

經(jīng)過計算,在hash函數(shù)設(shè)計合理的情況下,發(fā)生hash碰撞8次的幾率為百萬分之6,從概率上講,閾值為8足夠用;至于為什么紅黑樹轉(zhuǎn)回來鏈表的條件閾值是6而不是7或9?因為如果hash碰撞次數(shù)在8附近徘徊,可能會頻繁發(fā)生鏈表和紅黑樹的互相轉(zhuǎn)化操作,為了預(yù)防這種情況的發(fā)生。

插入元素的邏輯

  • 計算插入元素Key的Hash值
  • 判斷HashMap的數(shù)組是否為空,為空則初始化數(shù)組,并返回數(shù)組長度,不為空直接返回數(shù)組長度
  • 確定數(shù)據(jù)插入位置,數(shù)據(jù)插入位置=Key的Hash值 & (HashMap數(shù)組長度-1)
  • 根據(jù)插入位置,取出舊元素,判斷元素是否為空
  • 為空則初始化數(shù)據(jù)為鏈表頭節(jié)點,然后插入數(shù)組
  • 如果數(shù)據(jù)不為空,判斷新、舊元素的Key是否相等
  • 相等則將新插入的數(shù)據(jù)覆蓋舊的數(shù)據(jù),然后結(jié)束插入操作。
  • 不相等,則判斷取出的數(shù)據(jù)是鏈表頭節(jié)點還是紅黑樹頭節(jié)點。
  • 如果是鏈表,則將新插入數(shù)據(jù)插入鏈表尾部,插入成功后判斷鏈表長度是大于等于8,如果大于等于8則轉(zhuǎn)換成紅黑樹。
  • 如果是紅黑樹,則將數(shù)據(jù)插入紅黑樹中。
  • 元素插入成功這之后,HashMap長度+1。
  • 判斷HashMap的數(shù)據(jù)容量是否大于擴容閾值,如果大于則觸發(fā)HashMap擴容。


    HashMap-PUT操作

HashMap線程不安全問題

  • jdk 1.7中HashMap擴容會產(chǎn)生死循環(huán)(元素從鏈表頭部插入)、數(shù)據(jù)丟失、數(shù)據(jù)覆蓋的問題
  • jdk1.8 中HashMap 會有數(shù)據(jù)覆蓋的問題(元素從鏈表的尾部插入)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載于:https://segmentfault.com/a/1190000012926722 1.概述 本篇文章...
    無色的葉閱讀 769評論 2 7
  • 前文:HashMap是Java程序員最常用的映射(鍵值對)處理數(shù)據(jù)的容器。隨著JDK版本的更新,1.8相較于1.7...
    是一動不動的friend閱讀 1,331評論 0 1
  • 目錄 構(gòu)造器 構(gòu)造器只是初始化負載因子,和擴容閾值。并沒有初始化table。 閾值是比傳入的大的最小2的冪次方(傳...
    后來丶_a24d閱讀 516評論 0 7
  • 散列表 定義:通過散列函數(shù)把元素的鍵值映射為下標(biāo),然后將數(shù)據(jù)存儲在數(shù)組中對應(yīng)下標(biāo)的位置。當(dāng)我們按照鍵值查詢元素時,...
    回憶只能等候閱讀 217評論 0 0
  • HashMap概述 Hash,又稱散列。哈希表是一種以鍵-值(key-value) 存儲數(shù)據(jù)的,和數(shù)組、鏈表、二叉...
    99793933e682閱讀 622評論 0 6

友情鏈接更多精彩內(nèi)容