Java Hashtable類源碼解析

老生常談的問題——Hashtable和HashMap有什麼區別java

你們通常都能說出幾條,好比Hashtable是線程安全的,不支持null做爲key和value值等等。那麼,要仔細瞭解這個問題仍是直接從Hashtable的源碼入手。數組

先列一下我找到的區別:安全

  1. 繼承類不一樣,Hashtable繼承的是Dictionary這是一個廢棄類,而HashMap繼承的是AbstractMap
  2. 產生時間不一樣,Hashtable自JDK1.0版本就有了,而HashMap是JDK1.2才加入的,同時Hashtable可能由於歷史緣由並非咱們習慣的駝峯法命名的
  3. Hashtable比HashMap多提供了elments()方法用於返回此Hashtable中的value的枚舉
  4. Hashtable既不支持null key也不支持null value
  5. Hashtable的默認大小是11,擴大的邏輯是*2+1,對於給定大小不會作擴展。而HashMap是16,擴大時*2,初始大小會轉換成剛好大於等於的2的指數次冪
  6. Hashtable中的遍歷操做是從高位開始的,而HashMap是從低位開始
  7. Hashtable處理衝突元素時插入到鏈表頭部,而HashMap是插入到鏈表尾部
  8. Hashtable的hashcode方法計算全部entry的hashcode總和,HashMap沒有這樣的方法,同時HashMap在計算hash值時會用高位右移16位與低位異或來打散散列值,避免位與操做形成衝突過多
  9. Hashtable每一次定位都要作一次完整的除法取餘數,而HashMap使用的是與數組大小-1的位與計算,效率高不少
  10. Hashtable的方法都加上了synchronized是線程安全的方法,而HashMap不是,因此單線程時前者額外開銷很大。JDK8之後Hashtable也用了modCount來保證在遍歷過程當中其餘線程修改對象的fast-fail機制。可是,即便是多線程環境下,依然應該優先選擇對HashMap進行一些特殊處理而不是用Hashtable,由於全部方法都加上synchronized的程序併發性不好。實際上就我我的經驗而言,在一些特定的具體狀況下,好比大規模寫入key值連續數據(出自今年的第四屆阿里中間件性能挑戰賽複賽題),鏈表法解決衝突性能可能不如開放地址法,即便加上了紅黑樹。因此說對於一些對極致壓榨性能的狀況下,適當的能夠拋棄一些通用的集合而嘗試自由發揮造輪子。

 

首先從最上方的註釋中能夠看到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能夠看出,Hashtableindex計算邏輯是使用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進行修改。

相關文章
相關標籤/搜索