HashMap、HashTable和ConcurrentHashMap的區別

HashMap、HashTable

簡單點來說,HashTable是線程安全的HashMap,都實現了Map接口,Map接口對鍵值對進行映射。但還是有些不同,這裏從三點來說:線程安全性,同步(synchronization),以及速度。

  • 我們先來看看HashMap的底層:

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

不難看出, HashMap實現了 Cloneable, Serializable兩大接口;

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

 

以下爲hashmap的方法,有兩個很明顯的特點:

  1. 沒有加synchronized;因此線程不安全
  2. 它的鍵值可以爲空;
  3. Map中不允許重複的鍵;
public int size()   //鍵值對的數量
public boolean isEmpty()  //判斷是否爲空
public V get(Object key)  //常用的get方法,通過鍵獲取值
private V getForNullKey(Object key)  //通過鍵查看哪個值爲空 
private V putForNullKey(V value) //通過值查看哪個鍵爲空 
public boolean containsKey(Object key) //包含某個鍵
final Entry<K,V> getEntry(Object key) //hashmap中通過key拿到鍵值對
public V put(K key, V value)  //常用的put方法,將鍵值對放入map中
public V remove(Object key)  //移除掉當前的鍵值對
public void clear()  //清空map
public boolean equals(Object obj)
  •  HashTable的底層

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {

 HashTable也實現了 Cloneable, Serializable兩大接口,再次證明HashTable和HashMap都實現的Map接口;

底層描述:

  1. 和HashMap一樣,Hashtable 也是一個散列表,它存儲的內容是鍵值對。
  2. 從底層代碼看出,Hashtable 繼承於Dictionary,實現了Map、Cloneable、java.io.Serializable接口。
  3. Hashtable 它是線程安全的。它的key、value都不可以爲null

 

下面我們來看看HashTable中的方法:

  1. synchronized;因此線程安全
  2. 它的鍵值不可以爲空;
  3. HashTable和HashMap用法相似

public synchronized int size();
public synchronized boolean isEmpty()
public synchronized Enumeration<K> keys()
public synchronized boolean contains(Object value)
public boolean containsValue(Object value) //是否包含值
public synchronized boolean containsKey(Object key) //是否包含key
public synchronized V get(Object key)   //通過key拿到value
public synchronized V remove(Object key)  //通過key移除掉當前鍵值對
public synchronized void clear()  //清空map

很明顯都加了synchronized來保證線程安全

在這裏對synchronized做一些拓展,sychronized意味着在一次僅有一個線程能夠更改Hashtable。就是說任何線程要更新Hashtable時要首先獲得同步鎖,其它線程要等到同步鎖被釋放之後才能再次獲得同步鎖更新Hashtable。
 

總結:

  • 你需要完全的線程安全的時候使用HashTable,否則HashMap

問:我們能否讓HashMap同步?

  • Map m = Collections.synchronizeMap(hashMap);

 

ConcurrentHashMap

當我們需要線程安全時又想要很高的性能,此時就需要使用ConcurrentHashMap了,那麼爲什麼不適用穩定安全的hashtable呢?

我們先對ConcurrentHashMap做一些介紹:

  • 底層採用分段的數組+鏈表實現,線程安全。
  • 通過把整個map分爲N個Segment,可以提供相同的線程安全效率提升N倍,默認16倍。
  • 讀操作不加鎖,修改操作加分段鎖,允許多個修改操作並行發生。
  • 擴容:段內擴容(段內元素超過該段對應的Entry數組的0.75,觸發擴容,而不是整段擴容),插入前檢測是否需要擴容,避免無效擴容。

現在明白了,它們都可以用於多線程的環境,但是當Hashtable的大小增加到一定的時候,性能會急劇下降,而ConcurrentHashMap引入了分段鎖,不論它變得多麼大,僅僅需要鎖定map的某個部分,而其它的線程不需要等到迭代完成才能訪問map。簡而言之,在迭代的過程中,ConcurrentHashMap僅僅鎖定map的某個部分,而Hashtable則會鎖定整個map。

這裏我們對分段鎖進行介紹:

        首先將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。

面試中這些是常用的問題,在這裏我例舉幾個:

問:爲什麼String作爲鍵?

因爲String是不可變的,也是final的,而且已經重寫了equals()和hashCode()方法了。

問:hashmap如何處理碰撞的?

  • 由於hashmap在存值的時候並不是直接使用的key的hashcode,而是通過哈希算法計算出了一個新的hash值,這個計算出的hash值可以明顯的減少碰撞。
  • 擴容,擴容其實很好理解,就是將原來桶的容量擴爲原來的兩倍。這樣爭取散列的均勻,比如:原來桶的長度爲16,hash值爲1和17的entry將會都在桶的0號位上,這樣就出現了碰撞,而當桶擴容爲原來的2倍時,hash值爲1和17的entry分別在1和17號位上,岔開了碰撞。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章