源計劃之從HashMap認識資料結構

熊大蝦發表於2019-01-31

之前一直說看HashMap原始碼一直沒有看過,以至於面試時候,沒能夠說清楚很多問題,現在面試基本都會問到的一道問題,就是HashMap原始碼,你是否看過HashMap原始碼,HashMap的具體資料結構是怎麼的。

大學學習過資料結構,但當時學習得非常抽象,在將來的工作中,資料結構又是在程式裡面如何體現的呢。

我之前去學習HashMap原始碼都是看前輩們分析的部落格,從來沒有自己去看過,而且也從來沒有分析過,對於HashMap的原理也非常模糊。

首先我們需要了解HashMap是種什麼資料結構,才能明白具體實現k-v鍵值對存放資料的原理。

源計劃之從HashMap認識資料結構

大概資料結構如上,HashMap會是一個陣列加連結串列的資料結構,put的時候,會根據key值計算出陣列下標index。可以理解為這個時候,k-v鍵值對決定存入到哪個具體的連結串列上,具體我們看原始碼(本文僅僅先對JDK1.8之前的HashMap原始碼進行分析):

  • 分析原始碼之前,先明確幾個引數:
    • static final int DEFAULT_INITIAL_CAPACITY = 1 << 4:預設初始化大小16,該預設大小為陣列結構的大小
    • static final int MAXIMUM_CAPACITY = 1 << 30:陣列結構的最大大小,即便設定陣列結構超過該大小,陣列大小依然會預設此值
    • static final float DEFAULT_LOAD_FACTOR = 0.75f;:預設負載因子大小,陣列將以0.75倍擴容
    • static final Entry[] EMPTY_TABLE = {}:陣列+連結串列,為空時候的預設值
    • transient int size:
    • int threshold:發生擴容時候,陣列結構的大小
    • final float loadFactor:負載因子
      我們再來看put和get兩個方法:
源計劃之從HashMap認識資料結構

inflateTable(threshold):初始化一個陣列+連結串列結構

putForNullKey(value):key==null時候,會將這個鍵值對存放至陣列的第一位,

indexFor(hash,table.length):先用key計算出hash值,然後再利用hash計算出陣列具體的下標,也就是位置

addEntry(hash,key,value,i):將k-v鍵值對存放到桶中,可理解為存放到具體的陣列的某個具體連結串列的具體位置

這裡有兩個重點問題:

hash碰撞:所謂hash碰撞,簡單來說指得就是根據key值計算所得hash值相同,這樣一來就會發生hash碰撞,那麼hashmap如何解決這種hash碰撞的呢?這樣我們就需要具體去看看addEntry(hash,key,value,i)方法。

源計劃之從HashMap認識資料結構

當兩個不同的鍵卻有相同的hashCode時,他們會儲存在同一個bucket位置的連結串列中,Entry就是HashMap中連結串列最後的面紗。

源計劃之從HashMap認識資料結構

擴容機制:

擴容機制就很簡單,主要是對陣列結構進行擴容,主要是兩倍的陣列大小*loadFactor(負載因子),不會超過最大容量。

這裡需要注意的是:HashMap很有可能存放了16個k-v鍵值對,也不會發生擴容,因為其中如果16個k-v鍵值對中發生hash碰撞,那麼這幾個k-v鍵值對就會發生在同一個桶的位置,也就是隻會佔用16個位置中的一個,並不會發生擴容現象。

源計劃之從HashMap認識資料結構

簡單來說,就是兩個key的hash值相同的時候且key值

源計劃之從HashMap認識資料結構

其他Map成員

其實很多時候,HashMap無法滿足線上需要,我們希望HashMap能執行緒安全,又或者我們希望Map可以通過value找到key,又或者我們希望Map可以排序,等等之類,如果動手去實現這些功能也不無可以,當時確實很多時候我們考慮的需求,有很多先輩們已經實現了。

  • 這是最基本的map,其實jdk還實現了其他的map,下面有常用的兩個:

CurrentHashMap:執行緒安全的HashMap,使用了分段鎖,在保證了執行緒安全的同時,也提升了效率

TreeMap:會對Map中的key進行排序,內部資料結構是紅黑樹

  • 我們常用到的一個開源架包Apache的common架包下是實現了很多集合結構的,那我們看看有哪些map結構:
    BidiMap:即雙向Map,可以通過key找到value,也可以通過value找到key。需要注意的是BidiMap中key和value都不可以重複;
    LinkedMap,可以維護條目順序的map
    MultiMap,一個key指向的是一組物件,add()和remove()的時候跟普通的Map無異,只是在get()時返回一個Collection,實現了一對多;
    LazyMap,即Map中的鍵/值對一開始並不存在,當被呼叫到時才建立。

這些Map在實現某些特定的需求,都可以直接拿來使用。

思想借鑑:JDK1.8版本以後,幾乎重寫了HashMap,下次分析一篇1.8後的HashMap和現在的對比,這種變化又是為何?Map使用了紅黑樹結構,從而優化HashMap的儲存;大量使用三元運算子。

相關文章