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的方法,有兩個很明顯的特點:
- 沒有加synchronized;因此線程不安全
- 它的鍵值可以爲空;
- 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接口;
底層描述:
- 和HashMap一樣,Hashtable 也是一個散列表,它存儲的內容是鍵值對。
- 從底層代碼看出,Hashtable 繼承於Dictionary,實現了Map、Cloneable、java.io.Serializable接口。
- Hashtable 它是線程安全的。它的key、value都不可以爲null。
下面我們來看看HashTable中的方法:
- 加synchronized;因此線程安全
- 它的鍵值不可以爲空;
-
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號位上,岔開了碰撞。