Java集合之Hashtable源碼解析

在進行Hashtable源碼解析以前,我先扔出Hashtable與HashMap有哪些區別?
1.關於null,HashMap容許key和value均可覺得null,而Hashtable則不接受key爲null或value爲null的鍵值對。
2.關於線程安全,HashMap是線程不安全的,Hashtable是線程安全的,由於Hashtable的許多操做函數都用synchronized修飾。
3.Hashtable與HashMap實現的接口一致,但Hashtable繼承Dictionary,而HashMap繼承自AbstractMap,即父類不一樣。
4.默認初始容量不一樣,擴容大小不一樣。HashMap的hash數組的默認大小是16,並且必定是2 的指數,增長方式old2;Hashtable中hash數組默認大小是11,增長的方式是old2+1。java

以前簡要分析過HashMap的代碼,關於HashMap可點擊:Java集合之HashMap源碼解析 .下面咱們開始進行Hashtable的分析,先看代碼清單1:android

public class HashtableTest {
    public static void main(String [] args){
        Hashtable<String, String> table=new Hashtable<>();
        Hashtable<String, String> table1=new Hashtable<>(16);
        Hashtable<String, String> table2=new Hashtable<>(16, 0.75f);
        HashMap<String,String>  map=new HashMap<>();
        Hashtable<String,String> table3=new Hashtable<>(map);
        table.put("T1", "1");
        table.put("T2", "2");
        table.put(null, "3");
        System.out.println();
        System.out.println(table.toString());

    }
}複製代碼

咱們看到在建立對象上,和HashMap的使用方式類似,咱們繼續看其內部的構造函數是怎麼樣的,如代碼清單2: 算法

private transient Entry<?,?>[] table;//數組
    private transient int count;//鍵值對的數量
    private int threshold;//閥值
    private float loadFactor;//加載因子
    private transient int modCount = 0;//修改次數
    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];//這裏是與HashMap的區別之一,HashMap中table
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        initHashSeedAsNeeded(initialCapacity);
    }

    public Hashtable(int initialCapacity) {//指定初始數組長度
        this(initialCapacity, 0.75f);
    }

    public Hashtable() {//從這裏能夠看出容量的默認值爲16,加載因子爲0.75f.
        this(11, 0.75f);
    }

    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }複製代碼

Hashtable的構造函數主要對table數組進行了初始化,這裏沒有什麼要拿出來詳細講的,咱們仍是看下put的流程,代碼清單3:數組

public synchronized V put(K key, V value) {//這裏方法修飾符爲synchronized,因此是線程安全的。
        if (value == null) {
            throw new NullPointerException();//value若是爲Null,拋出異常
        }
        Entry tab[] = table;
        int hash = hash(key);//hash裏面的代碼是hashSeed^key.hashcode(),null.hashCode()會拋出異常,因此這就解釋了Hashtable的key和value不能爲null的緣由。
        int index = (hash & 0x7FFFFFFF) % tab.length;//獲取數組元素下標,先對hash值取正,而後取餘。
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                V old = e.value;
                e.value = value;
                return old;
            }
        }

        modCount++;//修改次數。
        if (count >= threshold) {//鍵值對的總數大於其閥值
            rehash();//在rehash裏進行擴容處理

            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }
        Entry<K,V> e = tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
        return null;
    }
private int hash(Object k) {
        // hashSeed will be zero if alternative hashing is disabled.
        return hashSeed ^ k.hashCode();//在1.8的版本中,hash就直接爲k.hashCode了。
    }
protected void rehash() {
        int oldCapacity = table.length;
        Entry<K,V>[] oldMap = table;
        int newCapacity = (oldCapacity << 1) + 1;//擴容,若是默認值是11,則擴容以後,數組的長度爲23
        if (newCapacity - MAX_ARRAY_SIZE > 0) {//這裏的最大值和HashMap裏的最大值不一樣,這裏Max_ARRAY_SIZE的是由於有些虛擬機實現會限制數組的最大長度。
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<K,V>[] newMap = new Entry[newCapacity];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        boolean rehash = initHashSeedAsNeeded(newCapacity);

        table = newMap;
        for (int i = oldCapacity ; i-- > 0 ;) {//遷移鍵值對
            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                if (rehash) {
                    e.hash = hash(e.key);
                }
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = newMap[index];
                newMap[index] = e;
            }
        }
    }複製代碼

經過代碼清單2和3,咱們能夠看到Hashtable的數據結構和HashMap同樣是一個Entry的數組,數組元素是一個單鏈表,這裏展現出了一個很明顯的區別,即hash值沒有擾動處理。那怎麼解決衝突呢?Hashtable的索引求值公式實際上是:
((hashSeed^k.hashCode())&0x7FFFFFFF)%newCapacity——> hash&0x7FFFFFFF%newCapacity。hash&0x7FFFFFF是爲了保證正數,由於hashCode的值有可能爲負值,這樣說有可能會有同窗說,取正數直接用Math.abs不就好了。。。可是你肯定全部狀況下,abs都能保證輸出是正數嗎?來來舉個例子給你看看:
安全

這裏寫圖片描述
這裏寫圖片描述

哈哈,你還能說什麼。hash&0x7FFFFFFF是爲了不負值的出現,對newCapacity求餘是爲了使index在數組索引範圍以內。看到這估計就有人問了那麼HashMap中的hash&(tab.leng-1)怎麼解釋呢?若是不太明白,還請你們仔細看上篇文章,這裏簡單說一下,hash&(tab.length-1)實際上是對(hash&0x7FFFFFFF)%newCapacity的代碼級優化,簡而言之就是位運算的性能是優於求餘運算的。bash

細心的讀者可能會發現HashMap與Hashtable的最大值是不同的,上篇文章咱們說過HashMap的長度是2的指數值,而HashMap的數組的最大長度是:1<<30=1073741824,這個值是int範圍內2的指數值的最大值,不信能夠打印1<<31=-2147483648。關於Hashtable的數組的最大值源碼註釋中有說明,是指虛擬機實現對array的長度有限制,若是你們糾結於這個最大值,why the max value is Integer.MAX_SIZE-8,請參考下面兩個連接:數據結構

stackoverflow.com/questions/3…
stackoverflow.com/questions/3…
說完了Hash與最大值的梗,咱們就要來看看線程安全,Hashtable中的主要方法都加了synchronized關鍵字來修飾(沒有被顏色覆蓋的),以下:
多線程

這裏寫圖片描述
這裏寫圖片描述

前面咱們已經分析過存數據的過程,如今咱們一塊兒來看看取的過程。

public synchronized V get(Object key) {//沒有什麼特殊性,就是加了一個synchronized,就是根據index來遍歷索引處的單鏈表。
        Entry tab[] = table;
        int hash = hash(key);
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return e.value;
            }
        }
        return null;
    }複製代碼

鑑於Hashtable是歷史遺留的類,如今不多有人使用它,即便咱們在對線程安全有要求的場景中,也是經過使用ConcurrentHashMap來解決,而不是使用Hashtable 。這裏能夠簡要的說一下緣由:Hashtable使用synchronized來實現線程安全,效率不高,而ConcurrentHashMap採用鎖分段技術來實現線程安全,大大提升了效率。在多線程環境中,當A線程訪問Hashtable的put方法時,其餘線程是不能訪問諸如get,clear這些方法的,可是在ConcurrentHashMap中只要保證A線程與B線程不是持有一個段鎖,是能夠A線程訪問put時其餘線程同時訪問get操做。函數

最後咱們說一下Hashtable的初始容量爲何是11?Hashtable的擴容方式是:old*2+1,初始容量11,第一次擴容爲23,第二次擴容爲47,能夠看到Hashtable的容量確定是奇數,有一些更是爲質數。到這裏就涉及到了哈希算法相關的知識了,這裏就不展開說哈希算法相關的內容了。Hashtable之因此初始容量爲11(質數)和擴容方式保證爲奇數,是爲了散列得更均勻,也就是減小碰撞發生的概率。性能

轉載請註明出處:blog.csdn.net/android_jia…

相關文章
相關標籤/搜索