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關鍵字void
的Class
物件的引用。
我們可以想到,假如將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++;
}