1. 程式人生 > >[JDK1.6] JAVA集合 Hashtable 原始碼淺析

[JDK1.6] JAVA集合 Hashtable 原始碼淺析

文章目錄

原始碼來自 jdk1.6

(一) 簡介:

此類實現一個雜湊表,該雜湊表將鍵對映到相應的值。任何非 null 物件都可以用作鍵或值。

為了成功地在雜湊表中儲存和獲取物件,用作鍵的物件必須實現 hashCode

方法和 equals 方法。

Hashtable 的例項有兩個引數影響其效能:初始容量 和載入因子。容量 是雜湊表中桶 的數量,初始容量 就是雜湊表建立時的容量。注意,雜湊表的狀態為 open:在發生“雜湊衝突”的情況下,單個桶會儲存多個條目,這些條目必須按順序搜尋。載入因子 是對雜湊表在其容量自動增加之前可以達到多滿的一個尺度。初始容量和載入因子這兩個引數只是對該實現的提示。關於何時以及是否呼叫 rehash 方法的具體細節則依賴於該實現。

通常,預設載入因子(.75)在時間和空間成本上尋求一種折衷。載入因子過高雖然減少了空間開銷,但同時也增加了查詢某個條目的時間(在大多數 Hashtable

操作中,包括 getput 操作,都反映了這一點)。

初始容量主要控制空間消耗與執行 rehash 操作所需要的時間損耗之間的平衡。如果初始容量大於 Hashtable 所包含的最大條目數除以載入因子,則永遠 不會發生 rehash 操作。但是,將初始容量設定太高可能會浪費空間。

如果很多條目要儲存在一個 Hashtable 中,那麼與根據需要執行自動 rehashing 操作來增大表的容量的做法相比,使用足夠大的初始容量建立雜湊表或許可以更有效地插入條目。

下面這個示例建立了一個數字的雜湊表。它將數字的名稱用作鍵:

   Hashtable<String, Integer> numbers = new Hashtable<String, Integer>();
   numbers.put("one", 1);
   numbers.put("two", 2);
   numbers.put("three", 3);

要獲取一個數字,可以使用以下程式碼:

   Integer n = numbers.get("two");
     if (n != null) {
         System.out.println("two = " + n);
     }

由所有類的“collection 檢視方法”返回的 collectioniterator 方法返回的迭代器都是快速失敗 的:在建立 Iterator 之後,如果從結構上對 Hashtable 進行修改,除非通過 Iterator 自身的 remove 方法,否則在任何時間以任何方式對其進行修改,Iterator 都將丟擲ConcurrentModificationException。因此,面對併發的修改,Iterator 很快就會完全失敗,而不冒在將來某個不確定的時間發生任意不確定行為的風險。由 Hashtable 的鍵和元素方法返回的 Enumeration 不 是快速失敗的。

注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步併發修改做出任何硬性保證。快速失敗迭代器會盡最大努力丟擲 ConcurrentModificationException。因此,為提高這類迭代器的正確性而編寫一個依賴於此異常的程式是錯誤做法:迭代器的快速失敗行為應該僅用於檢測程式錯誤。

從Java 2 平臺 v1.2起,此類就被改進以實現 Map 介面,使它成為 Java Collections Framework 中的一個成員。不像新的 collection 實現,Hashtable 是同步的

Hashtable 體系結構:

注意: Dictionary 類已經過時, 這裡不再討論, 我們只簡述 Map 的方法
在這裡插入圖片描述

Hashtable 的實現原理與 HashMap 原理幾乎沒有差異.有興趣的可以看這批文章:
HashMap 原始碼淺析

Hashtable 欄位屬性:

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

    /**
     * 用於存放雜湊表的資料
     */
    private transient Entry[] table;

    /**
     * 雜湊表中的條目總數。
     */
    private transient int count;

    /**
     * (閾值)
     * 當表的大小超過此閾值時,表將重新進行。 (該欄位的值為 (int)(capacity * loadFactor)。)
     */
    private int threshold;

    /**
     * 雜湊表的載入因子.(當表的元素數量 count > (table.length * loadFactor) 時進行擴容)
     */
    private float loadFactor;

    /**
     * 此Hashtable已被結構修改的次數結構修改是指更改Hashtable中的條目數或以其他方式修改其內部結構
     * (例如,重新雜湊)的修改。 此欄位用於在Hashtable的Collection-views上快速生成迭代器。 
     * (請參閱ConcurrentModificationException)。
     */
    private transient int modCount = 0;

    // Views

    /**
     * 初始化每個欄位以在第一次請求此檢視時包含相應檢視的例項。 
     * 檢視是無狀態的,因此沒有理由建立多個檢視。
     */
    private transient volatile Set<K> keySet = null;
    private transient volatile Set<Map.Entry<K,V>> entrySet = null;
    private transient volatile Collection<V> values = null;
}

Hashtabl 儲存 (鍵-值) 的節點 Entry:

    private static class Entry<K,V> implements Map.Entry<K,V> {
		int hash;			// key 的hash值
		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;
		}
		
		其他方法省略......
    }

構造方法:

預設容量 11, 負載因子loadFactor 為 0.75的構造方法

    public Hashtable() {
		this(11, 0.75f);
    }

指定初始容量的構造方法,預設負載因子loadFactor 為 0.75

    public Hashtable(int initialCapacity) {
		this(initialCapacity, 0.75f);
    }

指定初始容量,或負載因子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);
	
	        if (initialCapacity==0)
	            initialCapacity = 1;
		this.loadFactor = loadFactor;
		table = new Entry[initialCapacity];
		threshold = (int)(initialCapacity * loadFactor);
    }
    public Hashtable(Map<? extends K, ? extends V> t) {
		this(Math.max(2*t.size(), 11), 0.75f);
		putAll(t);
    }

儲存 鍵-值 put(K, V)

據說 Hashtable 效能差, 因為Hashtable 是執行緒安全的是同步的;來看看原始碼就清楚了;
put 的方法被 synchronized 關鍵字整個套主了, 即存儲存資料是, 整個物件鎖住了.
這樣子效能當然會很差, 據說慢了 100 倍.
不允許 null 值;

    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;
		for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
		    if ((e.hash == hash) && e.key.equals(key)) {
			V old = e.value;
			e.value = value;
			return old;
		    }
		}
	
		modCount++;
		if (count >= threshold) {
		    // Rehash the table if the threshold is exceeded
		    rehash();
	
	        tab = table;
	        index = (hash & 0x7FFFFFFF) % tab.length;
		}
	
		// Creates the new entry.
		Entry<K,V> e = tab[index];
		tab[index] = new Entry<K,V>(hash, key, value, e);
		count++;
		return null;
    }

擴容 rehash:

Hashtable 每一次擴容後的容量為舊容量的 2倍 + 1;
table 陣列被查詢分配容量. 舊的table資料會被拷貝到行的table中;
首先迭代 table 陣列, 獲取 entry 鏈節點的第一個節點指定, 再迭代entry鏈的所有元素

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

	int newCapacity = oldCapacity * 2 + 1;
	Entry[] newMap = new Entry[newCapacity];

	modCount++;
	threshold = (int)(newCapacity * loadFactor);
	table = newMap;

	for (int i = oldCapacity ; i-- > 0 ;) {
	    for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
			Entry<K,V> e = old;
			old = old.next;
	
			int index = (e.hash & 0x7FFFFFFF) % newCapacity;
			e.next = newMap[index];
			newMap[index] = e;
	    }
	}
}

獲取資料 get(Object)

    public synchronized V get(Object key) {
		Entry tab[] = table;
		int hash = key.hashCode();
		int index = (hash & 0x7FFFFFFF) % tab.length;
		for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
		    if ((e.hash == hash) && e.key.equals(key)) {
				return e.value;
		    }
		}
		return null;
    }

移除資料 remove(Object)

    public synchronized V remove(Object key) {
		Entry tab[] = table;
		int hash = key.hashCode();
		int index = (hash & 0x7FFFFFFF) % tab.length;
		for (Entry<K,V> e = tab[index], 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;
    }

其他方法

獲取元素數量: size()

    public synchronized int size() {
		return count;
    }

判斷是否有元素: isEmpty()

    public synchronized boolean isEmpty() {
		return count == 0;
    }

toString() 方法: 內部實現掉用了迭代器完成展示元素的檢視

    public synchronized String toString() {
		int max = size() - 1;
		if (max == -1)
		    return "{}";
	
		StringBuilder sb = new StringBuilder();
		Iterator<Map.Entry<K,V>> it = entrySet().iterator();
	
		sb.append('{');
		for (int i = 0; ; i++) {
		    Map.Entry<K,V> e = it.next();
	            K key = e.getKey();
	            V value = e.getValue();
	            sb.append(key   == this ? "(this Map)" : key.toString());
		    sb.append('=');
		    sb.append(value == this ? "(this Map)" : value.toString());
	
		    if (i == max)
			return sb.append('}').toString();
		    sb.append(", ");
		}
    }

是否包含這個 value 值: contains()

    public synchronized boolean contains(Object value) {
		if (value == null) {
		    throw new NullPointerException();
		}
	
		Entry tab[] = table;
		for (int i = tab.length ; i-- > 0 ;) {
		    for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
				if (e.value.equals(value)) {
				    return true;
				}
		    }
		}
		return false;
    }

如果包含這個值返回 true: containsValue

    public boolean containsValue(Object value) {
		return contains(value);
    }

判斷是否包含這個鍵: containsKey

    public synchronized boolean containsKey(Object key) {
		Entry tab[] = table;
		int hash = key.hashCode();
		int index = (hash & 0x7FFFFFFF) % tab.length;
		for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
		    if ((e.hash == hash) && e.key.equals(key)) {
				return true;
		    }
		}
		return false;
    }

把另一個Map集合全部新增到這個map: putAll

    public synchronized void putAll(Map<? extends K, ? extends V> t) {
        for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
            put(e.getKey(), e.getValue());
    }

清空所有元素: clear()

    public synchronized void clear() {
		Entry tab[] = table;
		modCount++;
		for (int index = tab.length; --index >= 0; )
		    tab[index] = null;
		count = 0;
    }

總結:

正如你所看見的, Hashtable 支援併發操作, 但是它的效率是低下的, 因為它所有的方法都套上了 synchronized 關鍵字, 即方法被呼叫時,這個物件被鎖定了.要直到這個方法呼叫完成返回後才能執行其他方法.