集合——HashMap的工做原理

http://www.importnew.com/16301.html 好的連接html

  • HashMap的工做原理?java

1.HashMap的底層結構是數組加鏈表程序員

     a.HashMap包含一個Entry(key,value,next,hash)的內部類,key/value放入HashMap的時候都會被包裝成Entry的對象面試

   b.HashMap成員就有Entry數組,該數組的大小默認是16,永遠都是2的次方數,若是本身給出的不是2的次方數會轉換成大於並接近本身給的2的次方數。put(key,value)就是轉換成Entry對象並放入數組中。算法

2.put方法的實現;數組

     c.1 根據key的HashCode進行Hash運算,獲得hash值緩存

     c.2 根據hash值去肯定數組的位置,hash&(table.length-1)等價於hash%(table.length) length是2的次方數該公式成立,正獲取數組的位置<bucket>。安全

     c.3 若是這個位置沒有元素存在,直接包裝成Entry實例,給元素數組附值;多線程

         若是計算出的位置有元素已經存在,就會判斷key是否相同,若是相同就會覆蓋,而且遍歷整個鏈表併發

         若是都不覆蓋插入到鏈表的頭部for (Entry<K,V> e = table[i]; e != null; e = e.next)

     注意1:若是計算出來的位置相同,這就是衝突率,咱們要減小衝突率,由於一旦放入鏈表中,之後老是要遍歷鏈表,效率低,要儘可能把元素直接放入數組,而非鏈表。根據實際要求重寫hashCode和euals

    注意2:底層是數組,儘可能減小擴容,因此HashMap放入元素的時候應該估算數組的大小,減小擴容。HashMap中有加載因子默認0.75,好比默認大小16,那麼數組已經有12個元素,會自動擴容。

3.get方法的實現;

     經過key查找元素的算法與放入是同樣的。全部一旦key放入HashMap,就不該該修改跟hashCode和euquals方法生成相關的屬性的值了。

  • HashMap中解決碰撞的方法

   hashCode相同時,

   查看bucket位置是有元素已經存在,就會調用equals方法判斷key是否相同,若是相同就會覆蓋,而且遍歷整個鏈表

   若是都不覆蓋插入到鏈表的頭部for (Entry<K,V> e = table[i]; e != null; e = e.next)(bucket存放的是Entry對象,鍵值對)

  • equals()和hashCode()的應用,以及重要性,「若是兩個鍵的hashcode相同,你如何獲取值對象?」

    hashCode相同時,找到bucket位置以後,會調用keys.equals()方法去找到鏈表中正確的節點,最終找到要找的值對象。

  (hashCode是用於查找使用的,而equals是用於比較兩個對象的是否相等的)

  • 不可變對象的好處     

  • HashMap多線程的條件競爭

  • 從新調整HashMap的大小,rehashing過程

   HashMap的負載因子大小爲0.75,當一個map填滿了75%的bucket時候,自動擴容,將會建立原來HashMap大小的兩倍的bucket數組,來從新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫做rehashing,由於它調用hash方法找到新的bucket位置。

  • 與Hashtable區別—---見HashMap和Hashtable的區別


5.TreeMap(key,value)
key必須可以根據某種規則排序,經過兩種比較器,TreeSet中用的就是TreeMap

6.Hashtable

     6.1 和HashMap相比類的層次結構不同,都實現了Map接口基本操做相似

     6.2 HashMap中的key/value均可以是null,而Hashtable不能是null,會拋出NullPointException異常

     6.3 Hashtable是線程安全的(線程併發效率低,但安全,數據一致性高),HashMap是線程不安全(線程併發時效率高,但不安全,數據一致性低)

     6.4 Hashtable的子類,java.util.Properties

   
7.java.util.Collections類
該類封裝了一些對集合的操做都是靜態方法,Collections.sort,max,min

與Collection的關係,沒有關係,介紹二者功能,Colleciton全部父類接口,子類接口有List,Set



先來些簡單的問題

「你用過HashMap嗎?」 「什麼是HashMap?你爲何用到它?」

幾乎每一個人都會回答「是的」,而後回答HashMap的一些特性,譬如HashMap能夠接受null鍵值和值,而Hashtable則不能;HashMap是非synchronized;HashMap很快;以及HashMap儲存的是鍵值對等等。這顯示出你已經用過HashMap,並且對它至關的熟悉。可是面試官來個急轉直下,今後刻開始問出一些刁鑽的問題,關於HashMap的更多基礎的細節。面試官可能會問出下面的問題:

「你知道HashMap的工做原理嗎?」 「你知道HashMap的get()方法的工做原理嗎?」

但一些面試者可能能夠給出答案,「HashMap是基於hashing的原理,咱們使用put(key, value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。當咱們給put()方法傳遞鍵和值時,咱們先對鍵調用hashCode()方法,返回的hashCode用於找到bucket位置來儲存Entry對象。」這裏關鍵點在於指出,HashMap是在bucket中儲存鍵對象和值對象,做爲Map.Entry。這一點有助於理解獲取對象的邏輯。若是你沒有意識到這一點,或者錯誤的認爲僅僅只在bucket中存儲值的話,你將不會回答如何從HashMap中獲取對象的邏輯。這個答案至關的正確,也顯示出面試者確實知道hashing以及HashMap的工做原理。可是這僅僅是故事的開始,當面試官加入一些Java程序員天天要碰到的實際場景的時候,錯誤的答案頻現。下個問題多是關於HashMap中的碰撞探測(collision detection)以及碰撞的解決方法:

「當兩個對象的hashcode相同會發生什麼?」 從這裏開始,真正的困惑開始了,一些面試者會回答由於hashcode相同,因此兩個對象是相等的,HashMap將會拋出異常,或者不會存儲它們。而後面試官可能會提醒他們有equals()和hashCode()兩個方法,並告訴他們兩個對象就算hashcode相同,可是它們可能並不相等。一些面試者可能就此放棄,而另一些還能繼續挺進,他們回答「由於hashcode相同,因此它們的bucket位置相同,‘碰撞’會發生。由於HashMap使用鏈表存儲對象,這個Entry(包含有鍵值對的Map.Entry對象)會存儲在鏈表中。」這個答案很是的合理,雖然有不少種處理碰撞的方法,這種方法是最簡單的,也正是HashMap的處理方法。但故事尚未完結,面試官會繼續問:

「若是兩個鍵的hashcode相同,你如何獲取值對象?」 面試者會回答:當咱們調用get()方法,HashMap會使用鍵對象的hashcode找到bucket位置,而後獲取值對象。面試官提醒他若是有兩個值對象儲存在同一個bucket,他給出答案:將會遍歷鏈表直到找到值對象。面試官會問由於你並無值對象去比較,你是如何肯定肯定找到值對象的?除非面試者知道HashMap在鏈表中存儲的是鍵值對,不然他們不可能回答出這一題。

其中一些記得這個重要知識點的面試者會說,找到bucket位置以後,會調用keys.equals()方法去找到鏈表中正確的節點,最終找到要找的值對象。完美的答案!

許多狀況下,面試者會在這個環節中出錯,由於他們混淆了hashCode()和equals()方法。由於在此以前hashCode()屢屢出現,而equals()方法僅僅在獲取值對象的時候纔出現。一些優秀的開發者會指出使用不可變的、聲明做final的對象,而且採用合適的equals()和hashCode()方法的話,將會減小碰撞的發生,提升效率。不可變性使得可以緩存不一樣鍵的hashcode,這將提升整個獲取對象的速度,使用String,Interger這樣的wrapper類做爲鍵是很是好的選擇。

若是你認爲到這裏已經完結了,那麼聽到下面這個問題的時候,你會大吃一驚。「若是HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦?」除非你真正知道HashMap的工做原理,不然你將回答不出這道題。默認的負載因子大小爲0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)同樣,將會建立原來HashMap大小的兩倍的bucket數組,來從新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫做rehashing,由於它調用hash方法找到新的bucket位置。

若是你可以回答這道問題,下面的問題來了:「你瞭解從新調整HashMap大小存在什麼問題嗎?」你可能回答不上來,這時面試官會提醒你當多線程的狀況下,可能產生條件競爭(race condition)。

當從新調整HashMap大小的時候,確實存在條件競爭,由於若是兩個線程都發現HashMap須要從新調整大小了,它們會同時試着調整大小。在調整大小的過程當中,存儲在鏈表中的元素的次序會反過來,由於移動到新的bucket位置的時候,HashMap並不會將元素放在鏈表的尾部,而是放在頭部,這是爲了不尾部遍歷(tail traversing)。若是條件競爭發生了,那麼就死循環了。這個時候,你能夠質問面試官,爲何這麼奇怪,要在多線程的環境下使用HashMap呢?

熱心的讀者貢獻了更多的關於HashMap的問題:

  1. 爲何String, Interger這樣的wrapper類適合做爲鍵? String, Interger這樣的wrapper類做爲HashMap的鍵是再適合不過了,並且String最爲經常使用。由於String是不可變的,也是final的,並且已經重寫了equals()和hashCode()方法了。其餘的wrapper類也有這個特色。不可變性是必要的,由於爲了要計算hashCode(),就要防止鍵值改變,若是鍵值在放入時和獲取時返回不一樣的hashcode的話,那麼就不能從HashMap中找到你想要的對象。不可變性還有其餘的優勢如線程安全。若是你能夠僅僅經過將某個field聲明成final就能保證hashCode是不變的,那麼請這麼作吧。由於獲取對象的時候要用到equals()和hashCode()方法,那麼鍵對象正確的重寫這兩個方法是很是重要的。若是兩個不相等的對象返回不一樣的hashcode的話,那麼碰撞的概率就會小些,這樣就能提升HashMap的性能。

  2. 咱們可使用自定義的對象做爲鍵嗎? 這是前一個問題的延伸。固然你可能使用任何對象做爲鍵,只要它遵照了equals()和hashCode()方法的定義規則,而且當對象插入到Map中以後將不會再改變了。若是這個自定義對象時不可變的,那麼它已經知足了做爲鍵的條件,由於當它建立以後就已經不能改變了。

  3. 咱們可使用CocurrentHashMap來代替Hashtable嗎?這是另一個很熱門的面試題,由於ConcurrentHashMap愈來愈多人用了。咱們知道Hashtable是synchronized的,可是ConcurrentHashMap同步性能更好,由於它僅僅根據同步級別對map的一部分進行上鎖。ConcurrentHashMap固然能夠代替HashTable,可是HashTable提供更強的線程安全性。看看這篇博客查看Hashtable和ConcurrentHashMap的區別。

總結

HashMap的工做原理

HashMap基於hashing原理,咱們經過put()和get()方法儲存和獲取對象。當咱們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,讓後找到bucket位置來儲存值對象。當獲取對象時,經過鍵對象的equals()方法找到正確的鍵值對,而後返回值對象。HashMap使用鏈表來解決碰撞問題,當發生碰撞了,對象將會儲存在鏈表的下一個節點中。 HashMap在每一個鏈表節點中儲存鍵值對對象。

當兩個不一樣的鍵對象的hashcode相同時會發生什麼? 它們會儲存在同一個bucket位置的鏈表中。鍵對象的equals()方法用來找到鍵值對。

由於HashMap的好處很是多,我曾經在電子商務的應用中使用HashMap做爲緩存。由於金融領域很是多的運用Java,也出於性能的考慮,咱們會常常用到HashMap和ConcurrentHashMap。你能夠查看更多的關於HashMap的文章:

相關文章
相關標籤/搜索