hashmap的工作原理是近年來常見的j**a面試題。幾乎每個j**a程式設計師都知道hashmap,都知道**要用hashmap,知道hashtable和hashmap之間的區別,那麼為何這道面試題如此特殊呢?是因為這道題考察的深度很深。這題經常出現在高階或中高階面試中。投資銀行更喜歡問這個問題,甚至會要求你實現hashmap來考察你的程式設計能力。concurrenthashmap和其它同步集合的引入讓這道題變得更加複雜。讓我們開始探索的旅程吧!
「你用過hashmap嗎?」 「什麼是hashmap?你為什麼用到它?」
幾乎每個人都會回答「是的」,然後回答hashmap的一些特性,譬如hashmap可以接受null鍵值和值,而hashtable則不能;hashmap是非synchronized;hashmap很快;以及hashmap儲存的是鍵值對等等。這顯示出你已經用過hashmap,而且對它相當的熟悉。但是面試官來個急轉直下,從此刻開始問出一些刁鑽的問題,關於hashmap的更多基礎的細節。面試官可能會問出下面的問題:
「你知道hashmap的工作原理嗎?」 「你知道hashmap的get()方法的工作原理嗎?」
你也許會回答「我沒有詳查標準的j**a api,你可以看看j**a源**或者open jdk。」「我可以用google找到答案。」
但一些面試者可能可以給出答案,「hashmap是基於hashing的原理,我們使用put(key, value)儲存物件到hashmap中,使用get(key)從hashmap中獲取物件。當我們給put()方法傳遞鍵和值時,我們先對鍵呼叫hashcode()方法,返回的hashcode用於找到bucket位置來儲存entry物件。」這裡關鍵點在於指出,hashmap是在bucket中儲存鍵物件和值物件,作為map.entry。這一點有助於理解獲取物件的邏輯。如果你沒有意識到這一點,或者錯誤的認為僅僅只在bucket中儲存值的話,你將不會回答如何從hashmap中獲取物件的邏輯。這個答案相當的正確,也顯示出面試者確實知道hashing以及hashmap的工作原理。但是這僅僅是故事的開始,當面試官加入一些j**a程式設計師每天要碰到的實際場景的時候,錯誤的答案頻現。下個問題可能是關於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()方法去找到鍊錶中正確的節點,最終找到要找的值物件。完美的答案!
如果你認為到這裡已經完結了,那麼聽到下面這個問題的時候,你會大吃一驚。「如果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 tr**ersing)。如果條件競爭發生了,那麼就死迴圈了。這個時候,你可以質問面試官,為什麼這麼奇怪,要在多執行緒的環境下使用hashmap呢?:)
我們可以使用自定義的物件作為鍵嗎?這是前乙個問題的延伸。當然你可能使用任何物件作為鍵,只要它遵守了equals()和hashcode()方法的定義規則,並且當物件插入到map中之後將不會再改變了。如果這個自定義物件時不可變的,那麼它已經滿足了作為鍵的條件,因為當它建立之後就已經不能改變了。
我們可以使用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,但是對於其是如何實現的卻不是很了解,最近看了看hashmap的原始碼加上自己的理解寫了這篇文章方便以後回憶,寫得不好的地方請提醒。hashmap繼承了abstractmap類並且實現了map cloneable serializable三個介面。ps 並不是每乙個集合...
HashMap的工作原理
hashmap的工作原理 hashmap基於hashing原理,我們通過put 和get 方法儲存和獲取物件。當我們將鍵值對傳遞給put 方法時,它呼叫鍵物件的hashcode 方法來計算hashcode值,然後找到bucket位置來儲存值物件。當獲取物件時,通過鍵物件的equals 方法找到正確的...
HashMap的工作原理總結
hashmap的工作原理 hashmap基於hashing原理,我們通過put 和get 方法儲存和獲取物件。當我們將鍵值對傳遞給put 方法時,它呼叫鍵物件的hashcode 方法來計算hashcode,讓後找到bucket位置來儲存值物件。當獲取物件時,通過鍵物件的equals 方法找到正確的鍵...