揭開HashTable源碼神祕面紗(二)

一、Hashtable簡介

1.Hashtable是什麼

    與HashMap一樣,都是哈希表,以鍵值對形式存儲數據。

2.Hashtable有什麼特點

    不允許key值和value值爲null
    與HashMap不同的是,Hashtable是線程安全的,它很多API都加了synchronized 修飾符。正因爲如此,多個線程競爭同一把鎖,導致效率很低。

3.Hashtable有什麼缺點

二、Hashtable源碼

1.Hashtable結構

    通過源碼來分析Hashtable的結構。

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {
    //Hashtable的數據載體
    private transient Entry<?,?>[] table;
    //Hashtable長度
    private transient int count;
    //容量閾值,如果超過這個閾值會通過rehash方法擴容
    private int threshold;
    //負載因子
    private float loadFactor;
    //修改Hashtable的次數,用於快速失敗機制
    private transient int modCount = 0;
}
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;
        }
 }

    由上述代碼可以看出:
    (1)Hashtable繼承了Dictionary類,實現了Map, Cloneable, Serializable接口。
    (2)Hashtable是以單鏈表Entry爲基本元素的數組。

2.Hashtable的初始化
/**
 * 根據給定的容量和負載因子構建Hashtable
 * initialCapacity 容量
 * loadFactor 負載因子
 */
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);
	//如果負載因子等於零,則取1
    if (initialCapacity==0)
        initialCapacity = 1;
    this.loadFactor = loadFactor;
	//初始化Entry數組
    table = new Entry<?,?>[initialCapacity];
	/**
	 * 初始容量乘以負載因子得出的值與MAX_ARRAY_SIZE + 1相比較,取較小者;
     * 其中,MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	 * 這是能分配給數組的最大長度。一些虛擬機保留對象頭信息,分配一部分字節用於存儲對象頭。
	 */
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}

/**
 * 如果不指定負載因子,則默認0.75
 */
public Hashtable(int initialCapacity) {
    this(initialCapacity, 0.75f);
}
/**
 * 如果是無參構造函數,則默認容量爲11,負載因子爲0.75
 */
public Hashtable() {
    this(11, 0.75f);
}

/**
 *	根據給定的Map構建Hashtable,使得Hashtable的映射關係與Map相同。
 */
public Hashtable(Map<? extends K, ? extends V> t) {
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);
}
3.put方法
public synchronized V put(K key, V value) {
	//不允許value爲空
	if (value == null) {
		throw new NullPointerException();
	}

	//確定key不存在Hashtable中
	Entry<?,?> tab[] = table;
	//如果key爲null,則報空指針異常
	int hash = key.hashCode();
	/**
	 * 計算索引,hash & 0x7FFFFFFF是爲了當hash值爲負數時轉成正數
	 * 因爲用的是取餘運算,比HashMap計算索引的與運算(n - 1) & hash效率低。
	 * /
	int index = (hash & 0x7FFFFFFF) % tab.length;
	@SuppressWarnings("unchecked")
	Entry<K,V> entry = (Entry<K,V>)tab[index];
	//如果存在相同hash值和key的結點,則該結點更新value值
	for(; entry != null ; entry = entry.next) {
		if ((entry.hash == hash) && entry.key.equals(key)) {
			V old = entry.value;
			entry.value = value;
			return old;
		}
	}

	//如果不存在相同hash值和key的結點,則新增結點
	addEntry(hash, key, value, index);
	return null;
}
private void addEntry(int hash, K key, V value, int index) {
	//Hashtable修改次數加一
	modCount++;

	Entry<?,?> tab[] = table;
	//如果Hashtable長度大於等於容量閾值,則擴容
	if (count >= threshold) {
		//擴容,下面會詳細介紹
		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);
	//Hashtable長度加一
	count++;
}
4.get方法
/*
 * 根據key和hash值找到相應的結點,並返回結點的value
 */
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;
    }
5.resize方法
protected void rehash() {
	//舊錶長
	int oldCapacity = table.length;
	//舊錶
	Entry<?,?>[] oldMap = table;

	//舊錶長乘2加1後賦值給新表容量newCapacity
	int newCapacity = (oldCapacity << 1) + 1;
	//如果新表容量大於MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
	if (newCapacity - MAX_ARRAY_SIZE > 0) {
		/**
		 * 如果舊錶長等於MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,則不需要擴容
		 * 否則,新表容量取MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
		 */
		if (oldCapacity == MAX_ARRAY_SIZE)
			return;
		newCapacity = MAX_ARRAY_SIZE;
	}
	//以新表容量重新初始化一個新表
	Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

	//修改次數加一
	modCount++;
	//新表容量乘以負載因子後與MAX_ARRAY_SIZE + 1比較,取較小值作爲新的容量閾值
	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;
		}
	}
}

三、額外知識點

1.HashMap和Hashtable有什麼區別?

    (1)HashMap繼承AbstractMap,而Hashtable繼承Dictionary
    (2)HashMap允許key和value爲空,而Hashtable不允許
    (3)HashMap線程不安全,而Hashtable線程安全,因爲加了synchronized修飾符
       關於HashMap線程不安全可以閱讀這篇博客(http://www.importnew.com/21396.html)
    (4)HashMap默認容量是16,而Hashtable默認容量是11
    (5)HashMap容量是2的整數次冪,而Hashtable容量可以是任意正整數
    (6)HashMap計算索引的方式是與運算,而Hashtable是取餘運算,效率較低
    (7)HashMap擴容後是原容量的2倍,而Hashtable擴容後是原容量的2倍+1

如有紕漏,敬請指正,筆者虛心接受。

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