1. 程式人生 > >HashTable原理和底層實現

HashTable原理和底層實現

1. 概述

上次討論了HashMap的結構,原理和實現,本文來對Map家族的另外一個常用集合HashTable進行介紹。HashTable和HashMap兩種集合非常相似,經常被各種面試官問到兩者的區別。

對於兩者的區別,主要有以下幾點:

  1. HashMap是非同步的,沒有對讀寫等操作進行鎖保護,所以是執行緒不安全的,在多執行緒場景下會出現資料不一致的問題。而HashTable是同步的,所有的讀寫等操作都進行了鎖(synchronized)保護,在多執行緒環境下沒有安全問題。但是鎖保護也是有代價的,會對讀寫的效率產生較大影響。
  2. HashMap結構中,是允許儲存null的,Entry.key
    Entry.value均可以為null。但是HashTable中是不允許儲存null的。
  3. HashMap的迭代器(Iterator)是fail-fast迭代器,但是Hashtable的迭代器(enumerator)不是fail-fast的。如果有其它執行緒對HashMap進行的新增/刪除元素,將會丟擲ConcurrentModificationException,但迭代器本身的remove方法移除元素則不會丟擲異常。這條同樣也是Enumeration和Iterator的區別。 2. 原理

    HashTable類中,儲存實際資料的,依然是Entry物件。其資料結構與HashMap是相同的。 HashTable類繼承自Dictionary

    類,實現了三個介面,分別是MapCloneablejava.io.Serializable,如下圖所示。

HashTable中的主要方法,如putgetremoverehash等,與HashMap中的功能相同,這裡不作贅述,可以參考另外一篇文章HashMap原理和底層實現

3. 原始碼分析

HashTable的主要方法的原始碼實現邏輯,與HashMap中非常相似,有一點重大區別就是所有的操作都是通過synchronized鎖保護的。只有獲得了對應的鎖,才能進行後續的讀寫等操作。

1. put方法

put方法的主要邏輯如下:

  1. 先獲取synchronized鎖。
  2. put方法不允許null
    值,如果發現是null,則直接丟擲異常。
  3. 計算key的雜湊值和index
  4. 遍歷對應位置的連結串列,如果發現已經存在相同的hash和key,則更新value,並返回舊值。
  5. 如果不存在相同的key的Entry節點,則呼叫addEntry方法增加節點。
  6. addEntry方法中,如果需要則進行擴容,之後新增新節點到連結串列頭部。
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;
    }
    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++;
    }

2. get方法

get方法的主要邏輯如下

  1. 先獲取synchronized鎖。
  2. 計算key的雜湊值和index。
  3. 在對應位置的連結串列中尋找具有相同hash和key的節點,返回節點的value。
  4. 如果遍歷結束都沒有找到節點,則返回null
public synchronized V get(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 (V)e.value;
            }
        }
        return null;
    }

3.rehash擴容方法

rehash擴容方法主要邏輯如下:

  1. 陣列長度增加一倍(如果超過上限,則設定成上限值)。
  2. 更新雜湊表的擴容門限值。
  3. 遍歷舊錶中的節點,計算在新表中的index,插入到對應位置連結串列的頭部。
    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;
            }
        }
    }

4.remove方法

remove方法主要邏輯如下:

  1. 先獲取synchronized鎖。
  2. 計算key的雜湊值和index。
  3. 遍歷對應位置的連結串列,尋找待刪除節點,如果存在,用e表示待刪除節點,pre表示前驅節點。如果不存在,返回null
  4. 更新前驅節點的next,指向e的next。返回待刪除節點的value值。

4. 總結

HashTable相對於HashMap的最大特點就是執行緒安全,所有的操作都是被synchronized鎖保護的

作者:道可 連結:https://www.imooc.com/article/details/id/23015 來源:慕課網 本文原創釋出於慕課網 ,轉載請註明出處,謝謝合作