HashTable源碼閱讀

環境jdk1.8.0_121html

與HashMap有幾點區別(不瞭解HashMap的具體實現,看我另個博客http://www.cnblogs.com/dj3839/p/8111675.htmljava

在HashMap中,衝突的值會在bucket造成鏈表,當達到8個,會造成紅黑樹,而在HashTable中,衝突的值就以鏈表的形式存儲數組

    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        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;
    }

會發現求索引的方式也不同,(hash&0x7FFFFFFF)%tab.length,而在HashMap中是(hash^(hash>>>16))&(tab.length-1),能夠看出HashTable裏,並無作出相應的優化,這邊解釋下HashMap中的優化,(hash^(hash>>>16))這一步是實際上是讓一個hash值的高16位和低16位作異或,混合高位和低位,加大低位的隨機性,(hash^(hash>>>16))&(tab.length-1)求與,其實就是至關於HashTable中的取模,只是在你計算機中用位預算效率比較高,固然tab.length在HashMap中實際上是一個2的n次方,因此能達到這一的效果。安全

還有一點,能夠看到HashTable中是不容許放值爲Null的value,它會拋出錯誤。並且key值也不能爲null,由於它直接拿key.hashCode(),null是拿不到hashCode也會發生錯誤。併發

繼續看addEntry,開始添加元素函數

    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++;
    }

代碼很是簡潔,若是數量大於限定值,就開始擴充,從新計算索引位置,而後插入高併發

先看插入優化

tab[index] = new Entry<>(hash, key, value, e);

在建立entry的時候,傳了個bucket的第一個entry,this

        protected Entry(int hash, K key, V value, Entry<K,V> next) {
            this.hash = hash;
            this.key =  key;
            this.value = value;
            this.next = next;
        }

看構造函數其實能夠看出,在這裏進行指向舊的第一個entry,所以,在hashtable中實際上是插入在鏈表的頭,而在HashMap是在尾spa

而後咱們在看它的rehash

    protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 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;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

比HashMap簡單的多。。。len擴充2*len+1,而後對原來bucket中的entry從新計算索引,並賦值,不改變鏈表原先的順序,在HashMap中複雜的多,能夠看我另個講HashMap的博客

並且在hashtable中,調用構造函數時,直接初始化了裏面的數組table,而在hashmap中是在進行put操做時,進行初始化,這個操做也在resize中

能夠看下HashTable中的初始化方法

    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);
    }

還有一點,HashTable它的初始化,默認容量len是爲11,後面也是2*len+1擴充,而HashMap是16,之後的擴充數量都是len*2,而且咱們提供容量大小時,也是會轉成一個2的n次方,爲何會有這樣的區分,和它計算hash有關,在前面提到了(hash^(hash>>>16))&(tab.length-1),2^n-1,二進制就是n個1

    public Hashtable() {
        this(11, 0.75f);
    }

但相對HashMap,HashTable是線程安全的,由於在不少方法,好比get,put,equals等,都使用了synchronized同步鎖。

總結:

1. HashTable的key、value不能爲null

2. HashTable線程安全

3. HashTable的優化其實沒有HashMap作的好,在單線程的狀況,最好使用HashMap

貼上一句源碼中的提示

 * Java Collections Framework</a>.  Unlike the new collection
 * implementations, {@code Hashtable} is synchronized.  If a
 * thread-safe implementation is not needed, it is recommended to use
 * {@link HashMap} in place of {@code Hashtable}.  If a thread-safe
 * highly-concurrent implementation is desired, then it is recommended
 * to use {@link java.util.concurrent.ConcurrentHashMap} in place of
 * {@code Hashtable}.

大體意思就是:不須要線程安全用HashMap,須要線程安全的高併發用ConcurrentHashMap

相關文章
相關標籤/搜索