面試必備:HashMap、Hashtable、ConcurrentHashMap的原理與區別

同步首發:http://www.yuanrengu.com/index.php/2017-01-17.html

若是你去面試,面試官不問你這個問題,你來找我^_^

下面直接來乾貨,先說這三個Map的區別:php

HashTable

  • 底層數組+鏈表實現,不管key仍是value都不能爲null,線程安全,實現線程安全的方式是在修改數據時鎖住整個HashTable,效率低,ConcurrentHashMap作了相關優化
  • 初始size爲11,擴容:newsize = olesize*2+1
  • 計算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap

  • 底層數組+鏈表實現,可以存儲null鍵和null值,線程不安全
  • 初始size爲16,擴容:newsize = oldsize*2,size必定爲2的n次冪
  • 擴容針對整個Map,每次擴容時,原來數組中的元素依次從新計算存放位置,並從新插入
  • 插入元素後才判斷該不應擴容,有可能無效擴容(插入後若是擴容,若是沒有再次插入,就會產生無效擴容)
  • 當Map中元素總數超過Entry數組的75%,觸發擴容操做,爲了減小鏈表長度,元素分配更均勻
  • 計算index方法:index = hash & (tab.length – 1)

 

HashMap的初始值還要考慮加載因子:html

  •  哈希衝突:若干Key的哈希值按數組大小取模後,若是落在同一個數組下標上,將組成一條Entry鏈,對Key的查找須要遍歷Entry鏈上的每一個元素執行equals()比較。
  • 加載因子:爲了下降哈希衝突的機率,默認當HashMap中的鍵值對達到數組大小的75%時,即會觸發擴容。所以,若是預估容量是100,即須要設定100/0.75=134的數組大小。
  • 空間換時間:若是但願加快Key查找的時間,還能夠進一步下降加載因子,加大初始大小,以下降哈希衝突的機率。

HashMap和Hashtable都是用hash算法來決定其元素的存儲,所以HashMap和Hashtable的hash表包含以下屬性:面試

  • 容量(capacity):hash表中桶的數量
  • 初始化容量(initial capacity):建立hash表時桶的數量,HashMap容許在構造器中指定初始化容量
  • 尺寸(size):當前hash表中記錄的數量
  • 負載因子(load factor):負載因子等於「size/capacity」。負載因子爲0,表示空的hash表,0.5表示半滿的散列表,依此類推。輕負載的散列表具備衝突少、適宜插入與查詢的特色(可是使用Iterator迭代元素時比較慢)

除此以外,hash表裏還有一個「負載極限」,「負載極限」是一個0~1的數值,「負載極限」決定了hash表的最大填滿程度。當hash表中的負載因子達到指定的「負載極限」時,hash表會自動成倍地增長容量(桶的數量),並將原有的對象從新分配,放入新的桶內,這稱爲rehashing。算法

HashMap和Hashtable的構造器容許指定一個負載極限,HashMap和Hashtable默認的「負載極限」爲0.75,這代表當該hash表的3/4已經被填滿時,hash表會發生rehashing。數組

「負載極限」的默認值(0.75)是時間和空間成本上的一種折中:安全

  • 較高的「負載極限」能夠下降hash表所佔用的內存空間,但會增長查詢數據的時間開銷,而查詢是最頻繁的操做(HashMap的get()與put()方法都要用到查詢)
  • 較低的「負載極限」會提升查詢數據的性能,但會增長hash表所佔用的內存開銷

程序猿能夠根據實際狀況來調整「負載極限」值。多線程

ConcurrentHashMap

  • 底層採用分段的數組+鏈表實現,線程安全
  • 經過把整個Map分爲N個Segment,能夠提供相同的線程安全,可是效率提高N倍,默認提高16倍。(讀操做不加鎖,因爲HashEntry的value變量是 volatile的,也能保證讀取到最新的值。)
  • Hashtable的synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔,ConcurrentHashMap容許多個修改操做併發進行,其關鍵在於使用了鎖分離技術
  • 有些方法須要跨段,好比size()和containsValue(),它們可能須要鎖定整個表而而不只僅是某個段,這須要按順序鎖定全部段,操做完畢後,又按順序釋放全部段的鎖
  • 擴容:段內擴容(段內元素超過該段對應Entry數組長度的75%觸發擴容,不會對整個Map進行擴容),插入前檢測需不須要擴容,有效避免無效擴容

 

Hashtable和HashMap都實現了Map接口,可是Hashtable的實現是基於Dictionary抽象類的。Java5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。併發

HashMap基於哈希思想,實現對數據的讀寫。當咱們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,而後找到bucket位置來存儲值對象。當獲取對象時,經過鍵對象的equals()方法找到正確的鍵值對,而後返回值對象。HashMap使用鏈表來解決碰撞問題,當發生碰撞時,對象將會儲存在鏈表的下一個節點中。HashMap在每一個鏈表節點中儲存鍵值對對象。當兩個不一樣的鍵對象的hashcode相同時,它們會儲存在同一個bucket位置的鏈表中,可經過鍵對象的equals()方法來找到鍵值對。若是鏈表大小超過閾值(TREEIFY_THRESHOLD,8),鏈表就會被改造爲樹形結構。性能

在HashMap中,null能夠做爲鍵,這樣的鍵只有一個,但能夠有一個或多個鍵所對應的值爲null。當get()方法返回null值時,便可以表示HashMap中沒有該key,也能夠表示該key所對應的value爲null。所以,在HashMap中不能由get()方法來判斷HashMap中是否存在某個key,應該用containsKey()方法來判斷。而在Hashtable中,不管是key仍是value都不能爲null。優化

Hashtable是線程安全的,它的方法是同步的,能夠直接用在多線程環境中。而HashMap則不是線程安全的,在多線程環境中,須要手動實現同步機制。

Hashtable與HashMap另外一個區別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。因此當有其它線程改變了HashMap的結構(增長或者移除元素),將會拋出ConcurrentModificationException,但迭代器自己的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這並非一個必定發生的行爲,要看JVM。

先看一下簡單的類圖:

  

從類圖中能夠看出來在存儲結構中ConcurrentHashMap比HashMap多出了一個類Segment,而Segment是一個可重入鎖。

ConcurrentHashMap是使用了鎖分段技術來保證線程安全的。

鎖分段技術:首先將數據分紅一段一段的存儲,而後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其餘段的數據也能被其餘線程訪問。 

ConcurrentHashMap提供了與Hashtable和SynchronizedMap不一樣的鎖機制。Hashtable中採用的鎖機制是一次鎖住整個hash表,從而在同一時刻只能由一個線程對其進行操做;而ConcurrentHashMap中則是一次鎖住一個桶。

ConcurrentHashMap默認將hash表分爲16個桶,諸如get、put、remove等經常使用操做只鎖住當前須要用到的桶。這樣,原來只能一個線程進入,如今卻能同時有16個寫線程執行,併發性能的提高是顯而易見的。

相關文章
相關標籤/搜索