老生常談的問題——Hashtable和HashMap有什麼區別java
你們通常都能說出幾條,好比Hashtable是線程安全的,不支持null做爲key和value值等等。那麼,要仔細瞭解這個問題仍是直接從Hashtable的源碼入手。數組
先列一下我找到的區別:安全
首先從最上方的註釋中能夠看到Hashtable自JDK1.0版本就有了,而HashMap是JDK1.2才加入的。觀察一下類的聲明,咱們能夠看到他們繼承的類也是不一樣的,Hashtable繼承的是Dictionary,Dictionary這個類從註釋上寫着已是obsolete被廢棄了,因此連帶Hashtable也基本不用了。Hashtable也有元素個數,數組大小,負載因子這些屬性,不用元素個數用的是count不是size。也是使用鏈表法來解決衝突。 多線程
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
構造函數能夠看出默認大小是11,同時初始大小給定多少初始數組就多大,不會作擴展到2的指數次冪這樣的操做。threshold=initialCapacity*loadFactor這點和HashMap相同。併發
public Hashtable(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry<?,?>[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); } public Hashtable() { this(11, 0.75f); }
contains這個方法是從表尾開始向前搜索的,同時也沒有使用==來比較函數
public synchronized boolean contains(Object value) { if (value == null) { throw new NullPointerException(); } Entry<?,?> tab[] = table; for (int i = tab.length ; i-- > 0 ;) { for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) { if (e.value.equals(value)) { return true; } } } return false; }
從containsKey能夠看出,Hashtable的index計算邏輯是使用key.hashCode()的後31位而後除以tab.length取餘數。HashMap的那種按位與的操做僅當操做數低位全是1時纔等價爲取餘操做,也就是2的指數次冪-1纔可成立,這樣作計算速度比除法快不少,不過沖突數量會增長,因此加入了一些打散的設計好比hashCode高位與低位異或。性能
public synchronized boolean containsKey(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return true; } } return false; }
擴展方法rehash的擴大方式是舊數組大小*2+1,而HashMap是*2,要從新計算每個的index因此效率低,同時衝突時將後面的元素插入到前面元素的前一位,因此會改變順序 this
protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1;//新大小=舊大小*2+1 if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];//建立一個新的數組 modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity;//從新計算每個元素的index e.next = (Entry<K,V>)newMap[index];//先後元素有衝突時,後面的元素插入到前面元素的前面 newMap[index] = e; } } }
對於插入結點一樣要先檢查是否存在key值相同的點,存在則不插入,而後檢查是否須要擴展數組,插入時若是發生衝突,也是將要插入的元素放在鏈表的首位,而putVal方法是放入尾部的。同時,能夠看到Hashtable是不支持null做爲key或value值的spa
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) {//value爲null直接報錯 throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode();//若key爲null這裏會報錯 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; } private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; }
Hashtable的hashcode方法計算全部entry的hash值總和線程
public synchronized int hashCode() { int h = 0; if (count == 0 || loadFactor < 0) return h; // Returns zero loadFactor = -loadFactor; // Mark hashCode computation in progress Entry<?,?>[] tab = table; for (Entry<?,?> entry : tab) { while (entry != null) { h += entry.hashCode(); entry = entry.next; } } loadFactor = -loadFactor; // Mark hashCode computation complete return h; }
elements這個方法是Hashtable多出來的,返回全部value值的枚舉
public synchronized Enumeration<V> elements() { return this.<V>getEnumeration(VALUES); }
咱們能夠注意到,Hashtable的方法都加上了synchronized,他們是線程安全的,可是對於自己是線程安全的狀況就會大幅度影響性能,JDK8開始引入modCount來做爲fast-fail機制,防止其餘線程的非synchronzied方法對Hashtable進行修改。