HashMap和HashTable的區別以及底層


區別
1、HashMap中的方法沒有synchronized修飾,線程非安全,而HashTable是線程安全的。
2、HashMap允許key和value為null,而HashTable不允許。
HashMap的底層實現
HashMap由數組+鏈表實現。
從jdk8開始,當鏈表高度達到8,數組長度達到64時,鏈表轉變為紅黑樹,元素以內部類Node節點存在。

計算key的hash值,二次hash后對數組長度取模,對應到數組下標。
如果該下標沒有值,那么直接將Node存入該位置。
如果有值,也就是產生了hash沖突,先進行equal比較,相同則取代該元素。不同的話就判斷鏈表高度插入鏈表。鏈表高度達到8,數組長度達到64時,鏈表轉變為紅黑樹,長度低於6時則紅黑樹轉變為鏈表。
如果key為null,存在下標為0的位置。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

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

區別:
(1)時間
hashTable是java發布的時候提供的鍵值映射的數據結構、hashMap是在jdk1.2之后出現的。但是hashTable現在幾乎棄用,雖然它是線程安全,但是ConcurrentHashMap卻可以替代並且效率更好
(2)提供的接口不同
hashtable相比hashMap多提供了2個接口elements()和contains()。其中elements()返回hashTable的中value的枚舉。contains()判斷傳入的value是否包含在hashTable中。
(3)繼承的類不同
hashMap繼承AbstractMap類、而hashTable繼承自Dictionary類。相同的是同實現了map、Cloneable、Serializable接口
(4)HashMap幾乎可以等價於Hashtable,除了HashMap是非synchronized的,並可以接受null(HashMap可以接受為null的鍵值(key)和值(value),而Hashtable則不行)。
(5)HashMap是非synchronized,而Hashtable是synchronized,這意味着Hashtable是線程安全的,多個線程可以共享一個Hashtable;而如果沒有正確的同步的話,多個線程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。
(6)另一個區別是HashMap的迭代器(Iterator)是fail-fast(快速失敗)迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以當有其它線程改變了HashMap的結構(增加或者移除元素),將會拋出ConcurrentModificationException,但迭代器本身的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這並不是一個一定發生的行為,要看JVM。這條同樣也是Enumeration和Iterator的區別。但是在jdk8之后hashTable的迭代器也加入了fail-fast迭代器。
(7)初始容量大小和每次擴充容量大小的不同
Hashtable默認的初始大小為11,之后每次擴充,容量變為原來的2n+1。HashMap默認的初始化大小為16。之后每次擴充,容量變為原來的2倍。
之所以會有這樣的不同,是因為Hashtable和HashMap設計時的側重點不同。Hashtable的側重點是哈希的結果更加均勻,使得哈希沖突減少。當哈希表的大小為素數時,簡單的取模哈希的結果會更加均勻。而HashMap則更加關注hash的計算效率問題。在取模計算時,如果模數是2的冪,那么我們可以直接使用位運算來得到結果,效率要大大高於做除法。HashMap為了加快hash的速度,將哈希表的大小固定為了2的冪。當然這引入了哈希分布不均勻的問題,所以HashMap為解決這問題,又對hash算法做了一些改動。這從而導致了Hashtable和HashMap的計算hash值的方法不同 。
(8)計算hash值的方法不同
為了得到元素的位置,首先需要根據元素的 KEY計算出一個hash值,然后再用這個hash值來計算得到最終的位置。

Hashtable直接使用對象的hashCode。hashCode是JDK根據對象的地址或者字符串或者數字算出來的int類型的數值。然后再使用除留余數發來獲得最終的位置。

Hashtable在計算元素的位置時需要進行一次除法運算,而除法運算是比較耗時的。
HashMap為了提高計算效率,將哈希表的大小固定為了2的冪,這樣在取模預算時,不需要做除法,只需要做位運算。位運算比除法的效率要高很多。

HashMap的效率雖然提高了,但是hash沖突卻也增加了。因為它得出的hash值的低位相同的概率比較高,而計算位運算。
為了解決這個問題,HashMap重新根據hashcode計算hash值后,又對hash值做了一些運算來打散數據。使得取得的位置更加分散,從而減少了hash沖突。當然了,為了高效,HashMap只做了一些簡單的位處理。從而不至於把使用2 的冪次方帶來的效率提升給抵消掉。

要注意的一些重要術語:

1) sychronized意味着在一次僅有一個線程能夠更改Hashtable。就是說任何線程要更新Hashtable時要首先獲得同步鎖,其它線程要等到同步鎖被釋放之后才能再次獲得同步鎖更新Hashtable。

2) Fail-safe(安全失敗)和iterator迭代器相關。如果某個集合對象創建了Iterator或者ListIterator,然后其它的線程試圖“結構上”更改集合對象,將會拋出ConcurrentModificationException異常。但其它線程可以通過set()方法更改集合對象是允許的,因為這並沒有從“結構上”更改集合。但是假如已經從結構上進行了更改,再調用set()方法,將會拋出IllegalArgumentException異常。

3) 結構上的更改指的是刪除或者插入一個元素,這樣會影響到map的結構。remove()不會

我們能否讓HashMap同步?

HashMap可以通過下面的語句進行同步:
Map m = Collections.synchronizeMap(hashMap);

常見面試題

HashMap的工作原理是近年來常見的Java面試題。幾乎每個Java程序員都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之間的區別,那么為何這道面試題如此特殊呢?是因為這道題考察的深度很深。這題經常出現在高級或中高級面試中。投資銀行更喜歡問這個問題,甚至會要求你實現HashMap來考察你的編程能力。ConcurrentHashMap和其它同步集合的引入讓這道題變得更加復雜。讓我們開始探索的旅程吧!

“你用過HashMap嗎?” “什么是HashMap?你為什么用到它?”

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

“你知道HashMap的工作原理嗎?” “你知道HashMap的get()方法的工作原理嗎?”

你也許會回答“我沒有詳查標准的Java API,你可以看看Java源代碼或者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的工作原理。但是這僅僅是故事的開始,當面試官加入一些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提供更強的線程安全性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM