? ? ? ? HashMap作為Java的一種特殊類型戴尸,在我們的編碼過程中被廣泛使用罚拟,專門用于存放類似于(key:value)這樣的數(shù)據(jù)亿卤。它就像一個糖罐子,只是它里面每項(xiàng)數(shù)據(jù)都由兩個值組成的热凹。在HashMap出來之前泵喘,java已經(jīng)存在可以用于存放(key:value)數(shù)據(jù)類型的對象,如Dictionary和Hashtable般妙,由于他們先前設(shè)計(jì)上的缺陷纪铺,目前Dictionary已經(jīng)廢棄了,而Hashtable也不怎么使用了碟渺。目前技術(shù)條件下的編碼過程中鲜锚,如果遇到(key:value)數(shù)據(jù)類型程序員們優(yōu)先考慮使用HashMap類型。接下來我們從頂層設(shè)計(jì)開始由淺入深一步一步認(rèn)識HashMap。下圖是Hash的設(shè)計(jì)類圖:
? ? ? ? 從圖中可以看出HashMap是繼承AbstractMap芜繁,并且HashMap的存儲內(nèi)容是Entity實(shí)體組成的旺隙,且都實(shí)現(xiàn)了自己特有的方法。那么具體使用的時候HashMap數(shù)據(jù)在內(nèi)存中是如何存儲的呢骏令?啥都不說我們先看下圖:
? ? ? ? 紫色部分即代表哈希表本身(其實(shí)是一個數(shù)組)蔬捷,數(shù)組的每個元素都是一個單鏈表的頭節(jié)點(diǎn),鏈表是用來解決hash地址沖突的伏社,如果不同的key映射到了數(shù)組的同一位置處抠刺,就將其放入單鏈表中保存塔淤。HashMap設(shè)計(jì)初衷為了提升通過key獲取value的效率摘昌,所以HashMap的Entity設(shè)計(jì)初衷是通過二叉樹-紅黑樹進(jìn)行存放的,只是HashMap的內(nèi)容過多就退化為單項(xiàng)鏈表了高蜂。接下來我們?nèi)タ匆豢淳唧w操作的流程聪黎,結(jié)合程序設(shè)計(jì)的流程圖HashMap的Put操作是如何實(shí)現(xiàn)的呢?
? ? ? ? 圖中顯示了HashMap的一個Put操作的流程备恤,應(yīng)用程序的put語句的后臺實(shí)現(xiàn)原理稿饰。這里面涉及到兩個HashMap重要的操作resize-擴(kuò)容和hash值計(jì)算。接下來分別講解這兩個操作:
? ? ? ? 擴(kuò)容露泊,簡單說就是HashMap有一張Hash表喉镰,Hash表是由數(shù)組構(gòu)成,在Java中數(shù)組的初始化需要制定長度惭笑,在HashMap中默認(rèn)初始化大小為16侣姆,即只能存放16個(key:vaule)內(nèi)容。16是開發(fā)HashMap的作者根據(jù)大多應(yīng)用場景得出的能應(yīng)付多數(shù)應(yīng)用場景的值沉噩,在數(shù)據(jù)量較大且無法估計(jì)HashMap大小的場景中就需要啟用擴(kuò)容機(jī)制捺宗。HashMap每次擴(kuò)充,容量變?yōu)樵瓉淼?倍川蒙。
? ? ? ? 擴(kuò)容涉及到擴(kuò)容的兩個重要參數(shù)initialCapacity和loadFactor蚜厉。initialCapacity設(shè)置初始化HashMap大小的值,默認(rèn)是16畜眨。loadFactor是加載因子昼牛,默認(rèn)是0.75。當(dāng)存儲個數(shù)threshold即size > initialCapacity *loadFactor康聂,hashmap內(nèi)部resize方法就被調(diào)用贰健。同樣initialCapacity和loadFactor也可以在實(shí)例化HashMap的時候調(diào)用構(gòu)造函數(shù)進(jìn)行初始化,但需要注意一下兩點(diǎn)早抠。
1.initialCapacity容量較大霎烙,那么會使迭代器效率降低。所以理想的情況還是在使用HashMap前估計(jì)一下數(shù)據(jù)量。
2.加載因子默認(rèn)值是0.75悬垃,是JDK權(quán)衡時間和空間效率之后得到的一個相對優(yōu)良的數(shù)值游昼。如果這個值過大,雖然空間利用率是高了尝蠕,但是對于HashMap中的一些方法的效率就下降了烘豌。
? ? ? ? 根據(jù)HashMap的實(shí)現(xiàn),它在每次擴(kuò)容后的容量是原先的一倍看彼。擴(kuò)充結(jié)果都為2的冪次方大小廊佩。即HashMap總是使用2的冪作為哈希表的大小。這樣做可以成倍提升計(jì)算Hash值的效率靖榕,為什么這么說呢标锄?
? ? ? ? 因?yàn)镠ashMap大小是2的冪次方,所以它使用取模計(jì)算時茁计,可以直接使用位運(yùn)算來得到結(jié)果料皇,效率大大高于以前使用除法的效率。比如在HashMap執(zhí)行增加星压、刪除践剂、查找鍵值對時,定位到哈希位置是很關(guān)鍵的一步娜膘,源碼中是通過下面3個操作來完成這一步:1)拿到key的hashCode值逊脯;2)將hashCode的高位參與運(yùn)算,重新計(jì)算hash值竣贪;3)將計(jì)算出來的hash值與(table.length - 1)進(jìn)行&運(yùn)算军洼。
? ? ? ? 雖然使用HashMap在效率上會有提升,但是為了提升效率贾富,HashMap在實(shí)現(xiàn)過程中未進(jìn)行線程安全性限制歉眷,所以HashMap是線程不安全的。具體的線程不安全體現(xiàn)在以下兩方面:
第一颤枪,如果多個線程同時使用put方法添加元素
假設(shè)正好存在兩個put的key發(fā)生了碰撞(hash值一樣)汗捡,那么根據(jù)HashMap的實(shí)現(xiàn),這兩個key會添加到數(shù)組的同一個位置畏纲,這樣最終就會發(fā)生其中一個線程的put的數(shù)據(jù)被覆蓋扇住。
第二,如果多個線程同時檢測到元素個數(shù)超過數(shù)組大小*loadFactor
這樣會發(fā)生多個線程同時對hash數(shù)組進(jìn)行擴(kuò)容盗胀,都在重新計(jì)算元素位置以及復(fù)制數(shù)據(jù)艘蹋,但是最終只有一個線程擴(kuò)容后的數(shù)組會賦給table,也就是說其他線程的都會丟失票灰,并且各自線程put的數(shù)據(jù)也丟失女阀。且會引起死循環(huán)的錯誤宅荤。
? ? ? ? HashMap給出了他自己的解決并發(fā)辦法,多線程需要使用HashMap建議采用concurrent并發(fā)包下的concurrentHashMap浸策。
? ? ? ? 今天和大家簡單的分享了Java的HashMap冯键,當(dāng)然HashMap還有很多特性我一文不可能全部列舉完整,希望和大家一起交流學(xué)習(xí)庸汗,共同進(jìn)步惫确。