Hashtable

Hashtable 是 JDK 中較早的數據結構了,目前已再也不推薦使用了。但抱着學習的目的,仍是看了下它的實現。java

簡介

Hashtable,顧名思義即哈希表,是一種經典的數據結構。其基本結構是一個數組,而數組中的每一個元素都是一個單向鏈表。哈希表的內部結構以下圖:

先解釋下 Hashtable 類中幾個變量屬性的含義:數組

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

/**
 * The total number of entries in the hash table.
 */
private transient int count;

/**
 * The table is rehashed when its size exceeds this threshold.  (The
 * value of this field is (int)(capacity * loadFactor).)
 *
 * @serial
 */
private int threshold;

/**
 * The load factor for the hashtable.
 *
 * @serial
 */
private float loadFactor;
  • table 即存放單向鏈表的數組;
  • count 表示哈希表的元素總數;
  • capacity 表示哈希表數組的總長度;
  • loadFactor 表示負載因子,用於平衡時間和空間,默認值爲:0.75
  • threshold 表示哈希表自動擴容的閾值,其值即爲:capacity * loadFactor

Hashtable 類爲了提升查詢速度,防止每一個元素的單向鏈表過長,使用了自動擴容機制,下面就詳細說說 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;
}

添加一個元素其實是調用了 addEntry 方法:this

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

能夠看到,在這個方法裏判斷了條件 count >= threshold ,也就是說當哈希表中的元素總數超過自動擴容閾值時就進行自動擴容。而實際的擴容方法則是 rehash:code

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

擴容的主要邏輯就是:blog

  1. 將當前容量值乘以 2 以後再加 1,計算獲得新的容量值;
  2. 若新容量值超過了哈希表容許的最大容量值,則取最大容量值;
  3. 以新容量值新生成一個數組;
  4. 遍歷舊數組中的每一個單向鏈表,遍歷單向鏈表上的每一個元素,而後從新計算哈希值,並放入新數組中;
相關文章
相關標籤/搜索