JDK原始碼閱讀(5):HashTable類閱讀筆記

pedro7發表於2021-11-09

HashTable

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

HashMap只實現了Map介面,而HashTable還繼承了Dictionary類。但實際上Dictionary類只是一個歷史遺留問題,任何新的鍵值對集合都只需要實現Map介面。

1. 構造方法

/**
 * Constructs a new, empty hashtable with a default initial capacity (11)
 * and load factor (0.75).
 */
public Hashtable() {
    this(11, 0.75f);
}

HashTable的預設容量是11,預設負載因子是0.75。HashMap的這兩個值分別是16和0.75。

2. Properties類和Void類

在閱讀到HashTable的建構函式時,我看到了一個奇怪的建構函式:

/**
 * A constructor chained from {@link Properties} keeps Hashtable fields
 * uninitialized since they are not used.
 *
 * @param dummy a dummy parameter
 */
Hashtable(Void dummy) {}

首先,在引數列表中出現了一個我從來沒見過的類:Void類。我們進入檢視這個類的定義。

package java.lang;

/**
 * The {@code Void} class is an uninstantiable placeholder class to hold a
 * reference to the {@code Class} object representing the Java keyword
 * void.
 *
 * @author  unascribed
 * @since   1.1
 */
public final class Void {

    /**
     * The {@code Class} object representing the pseudo-type corresponding to
     * the keyword {@code void}.
     */
    @SuppressWarnings("unchecked")
    public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void");

    /*
     * The Void class cannot be instantiated.
     */
    private Void() {}
}

可以看到,Void類是一個final類,同時只有一個私有的建構函式。顯然,這個類既不可以被繼承,也不可以被例項化。在doc中我們得知,這是一個佔位符類,用於儲存對錶示Java關鍵字voidClass物件的引用。

我們可以想到,假如將Void類作為一個方法的返回型別,這意味著這個方法只能返回null。

public Void f(){
    ...
}

那麼在這裡作為引數的Void類又起到怎樣的功能呢?繼續閱讀構造方法上方的doc,我們發現,這個函式是供java.util.Properties類使用的,Properties類中有下面這個建構函式。

private Properties(Properties defaults, int initialCapacity) {
    // use package-private constructor to
    // initialize unused fields with dummy values
    super((Void) null);
    map = new ConcurrentHashMap<>(initialCapacity);
    this.defaults = defaults;

    // Ensure writes can't be reordered
    UNSAFE.storeFence();
}

Properties類是HashTable類的子類,在這個建構函式中它呼叫了HashTable中包級私有的構造方法。如果沒有這個super語句的話,就會預設呼叫父類HashTable的無參建構函式,但顯然,這是沒有必要的。通過在HashTable中新增一個偽構造方法供Properties類呼叫,可以避免這種無必要的開銷。

這裡順便說明一下Properties類。

  • Properties類表示一組持久的屬性。表示一個持久的屬性集,屬性列表中每個鍵及其對應值都是一個字串。

  • Properties 類被許多 Java 類使用。例如,在獲取環境變數時它就作為 System.getProperties() 方法的返回值。

  • Properties 定義如下例項變數.這個變數持有一個 Properties 物件相關的預設屬性列表。

    Properties defaults;
    

3. HashTable中實現執行緒安全的方式

HashTable中為幾乎所有業務方法都新增了synchronized關鍵字,實現了執行緒安全。

public synchronized int size() {...}
public synchronized boolean isEmpty() {...}
public synchronized V put(K key, V value) {...}
public synchronized V get(Object key) {...}
public synchronized V remove(Object key) {...}

4. keys方法和elements方法

public synchronized Enumeration<K> keys() {
    return this.<K>getEnumeration(KEYS);
}

public synchronized Enumeration<V> elements() {
    return this.<V>getEnumeration(VALUES);
}

返回了鍵集合和值集合的列舉。

private <T> Enumeration<T> getEnumeration(int type) {
    if (count == 0) {
        return Collections.emptyEnumeration();
    } else {
        return new Enumerator<>(type, false);
    }
}

下面是作為HashTable類內部類的列舉類

private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
    final Entry<?,?>[] table = Hashtable.this.table;
    int index = table.length;
    Entry<?,?> entry;
    Entry<?,?> lastReturned;
    final int type;

    /**
     * Indicates whether this Enumerator is serving as an Iterator
     * or an Enumeration.  (true -> Iterator).
     */
    final boolean iterator;

    /**
     * The modCount value that the iterator believes that the backing
     * Hashtable should have.  If this expectation is violated, the iterator
     * has detected concurrent modification.
     */
    protected int expectedModCount = Hashtable.this.modCount;

    Enumerator(int type, boolean iterator) {
        this.type = type;
        this.iterator = iterator;
    }

    public boolean hasMoreElements() {
        ...
    }

    @SuppressWarnings("unchecked")
    public T nextElement() {
        Entry<?,?> et = entry;
        int i = index;
        Entry<?,?>[] t = table;
        /* Use locals for faster loop iteration */
        while (et == null && i > 0) {
            et = t[--i];
        }
        entry = et;
        index = i;
        if (et != null) {
            Entry<?,?> e = lastReturned = entry;
            entry = e.next;
            return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
        }
        throw new NoSuchElementException("Hashtable Enumerator");
    }

    // Iterator methods
    public boolean hasNext() {
        return hasMoreElements();
    }

    public T next() {
        if (Hashtable.this.modCount != expectedModCount)
            throw new ConcurrentModificationException();
        return nextElement();
    }

    public void remove() {
        ...
    }
}

5. HashTable中定位下標的方法

int index = (hash & 0x7FFFFFFF) % tab.length;

hash & 0x7FFFFFFF是為了保證hash值是一個正數,即二進位制下第一位是0。

然後直接對length取模。

6. rehash方法和addEntry方法

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

    // 擴容邏輯:乘二加一
    int newCapacity = (oldCapacity << 1) + 1;
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        if (oldCapacity == MAX_ARRAY_SIZE)
            // 原容量就滿了,則直接返回
            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。

下面的方法用於向表中假如鍵值對,在put等方法中都有呼叫。

private void addEntry(int hash, K key, V value, int index) {
    Entry<?,?> tab[] = table;
        if (count >= threshold) {
            // 先呼叫rehash()
            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++;
        modCount++;
}

相關文章