Hashtable源碼解析

Hashtable 算是平時用的比較少的一個集合了,先從繼承、實現關係 及 構造函數來簡單的瞭解一下

public class Hashtable<K,V>  extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {

    /**
     * The hash table data.
     */
    private transient Entry<?,?>[] table;

    /**
     * Hashtable 中實體數量
     */
    private transient int count;

    // 擴容閥值
    private int threshold;

    // 負載因子
    private float loadFactor;

    // 最大長度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    // 修改 Hashtable 的次數
    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];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

    // 初始化容量 構造器,默認負載因子 0.75f
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    // 無參構造器 , 初始化容量 11 負載因子 0.75f
    public Hashtable() {
        this(11, 0.75f);
    }

    // Map 構造器 初始化容量 以 2倍的Map 與 11 誰大取誰,負載因子 0.75f
    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

Hashtable 繼承了 Dictionary ,我們都知道 Dictionary 的意思是字典,那麼Hashtable 繼承了Dictionary 會不會使其有字典的特性呢? 我們先把這個問題放在這裏。全局變量 無非就是定義一些 擴容閥值 threshold 、負載因子 loadFactor、修改次數 modCount等,具體代表的含義已經在字段上註釋的很清晰了。

對於支持的構造函數,我看可以看到所有的構造函數最終都是調用的 包含對Hashtable的容量大小及負載因子的構造函數。重點看兩個,其一是無參構造函數,從這裏面我們看到Hashtable 的默認大小爲 11 負載因子爲 0.75f 。其二是Map構造函數,其實就是將Map中的所有實體元素 添加到Hashtable中,在此構造函數中,默認的負載因子仍爲0.75f,Hashtable 容量大小爲 Map 中元素個數的 2 倍 與默認大小 11 中取大值作爲初始化容量

以下我們仍以無參構造爲例,通過put 及remove 兩個函數來了解 Hashtable 的數據結構。

一、put() 函數

同樣我們先看源碼,便於瞭解

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

put 函數由 synchronized 修飾,說明 此函數在多線程環境下是安全的。源碼中第三行對value的判斷及第九行 獲取key的hash值,說明在Hashtable中 key 與 value 均不可爲 null 。源碼第十行,通過key的hash值計算出在Hashtable應該存儲的位置,通過下標index獲取當前位置上的元素,如果當前位置沒有元素,那麼就直接放進去即可。因爲在複雜的業務中 Hashtable 中的元素衆多,發生hash碰撞是經常的事情,那麼在 Hashtable 中是怎麼處理 hash 碰撞的呢,我們可以從 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++;
    }

上面對擴容閥值的判斷很好理解,就不一步步梳理了。直接看到最後,先拿到下標爲index的元素,然後創建一個新的實體(也可以叫做元素)並賦值給 當前下標。那麼到目前爲止,還沒有看到處理hash碰撞的具體過程,唯一的處理過程就可能在創建新的元素中了,我們繼續進入創建元素的函數一觀究竟:

    private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Entry<K,V> next;

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

從創建新的元素構造函數我們可以清楚地看到,新創建的元素放置在下標爲index的位置,而之前位於index的元素則被賦值給 新元素的next屬性。從這裏我們可以看到 所有的發生 hash 碰撞的元素,除了放置於index位置的元素,其他元素都會被另一個元素的next 所指向,這不就是一個單向鏈表嗎?而每次由新的元素髮生 hash 碰撞的時候 都會插入到這個單向鏈表的頭部。由於Hashtable 初始化時 帶層維護的是一個數組,所以我們可以知道 Hashtable 就是一個 數組 + 單向鏈表的 數據結構

從 addEntry 函數中可以看到,如果 count 大於或者等於 threshold 是 會進行一次 rehash 也就是我們理解的擴容操作,具體擴容的方式是怎樣的呢,我們還是從源碼中找到對應的邏輯:

    @SuppressWarnings("unchecked")
    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;
            }
        }
    }

重點就在第七行,擴容的的邏輯爲 原容量大小的 2倍加1 ,及 newCapacity = 2 * oldCapacity + 1 。

二、remove() 函數

    public synchronized V remove(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
        for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                modCount++;
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
                count--;
                V oldValue = e.value;
                e.value = null;
                return oldValue;
            }
        }
        return null;
    }

remove 函數就比較簡單了,源碼看起來也是非常簡明的,首先根據key獲取對應的hash值,然後獲取在Hashtable 中的下標,獲取下標爲index的單向連表,從頭部開始,獲取對應的元素,並將對應元素的父元素的next 指向 對應元素的next,呢麼該元素就相當於從這個鏈表中脫離掉,以達到刪除的目的。如果沒有找到對應的元素則不做任何操作。

整個鏈表的數據結構相信應該十分清楚了,最後附一張結構圖,以增加理解。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章