所使用的jdk版本爲1.8.0_172版本,先看一下 Hashtable<K,V> 在JDK中Map的UML類圖中的位置:
2.1 Hashtable<K,V> 類概述
上圖中的繼承實現關係不夠詳盡,先看一下 Hashtable 定義:
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
Hashtable繼承實現了抽象類Dictionary<K,V>類,並且實現了Map<K,V>,Cloneable、Serializable三個接口。Dictionary<K,V>抽象類JDK已經註明是一個過時的類,新的實現應該實現Map<K,V>接口,而不是繼承該類。
Hashtable存儲映射用的是內部私有靜態類 Entry<K,V>,基本思想就是 Entry<K,V>數組 加 Entry<K,V>鏈表 存儲映射數據。看一下 Entry<K,V> 的簡單定義:
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;
}
Hashtable<K,V>中的鍵K和值V不能是null,使用時用作鍵K的對象必須實現 hashCode
方法和 equals
方法。常用的Integer和String等都實現了這兩個方法,如果是我們自己定義的的類作爲Key,一定要記得實現。Hashtable是線程安全的,因爲它的方法比如put、get等方法都用synchronized關鍵字修飾了,不過因爲這種實現方式鎖比較重,因此官方推薦如果不需要線程安全的Map實現,則推薦使用 HashMap;如果需要支持高併發的Map實現,推薦使用 ConcurrentHashMap。
Hashtable中有兩個參數initialCapacity(初始容量)和 loadFactor(加載因子,float類型),兩者影響Hashtable何時擴容Entry數組的長度以及Rehash的過程。Entry數組的長度達到閥值threshold(threshold = initialCapacity * loadFactor),put方法中就會觸發擴容Rehash的過程,新的Entry數組長度 newCapacity = (oldCapacity * 2) + 1。
新數組的長度最大爲 Integer.MAX_VALUE - 8。爲什麼不是整型最大值,原因如下:
(Some VMs reserve some header words in an array,Attempts to allocate larger arrays may result in OutOfMemoryError: Requested array size exceeds VM limit)。
initialCapacity的默認值爲 11,loadFactor默認值爲 0.75f。
2.1.1 構造方法
Hashtable提供了四種構造方法:
/**
* 用指定初始容量和指定加載因子構造一個新的空哈希表。
*/
public Hashtable(int initialCapacity, float loadFactor)
/**
* 用指定初始容量和默認的加載因子 (0.75) 構造一個新的空哈希表。
*/
public Hashtable(int initialCapacity)
/**
* 用默認的初始容量 (11) 和加載因子 (0.75) 構造一個新的空哈希表
*/
public Hashtable()
/**
* 構造一個與給定的 Map 具有相同映射關係的新哈希表。
*/
public Hashtable(Map<? extends K, ? extends V> t)
2.1.2 put 方法
public synchronized V put(K key, V value);
將指定的K-V映射關係放入Hashtable中,注意方法使用了synchronized關鍵字修飾。
入參:鍵和值都不可以爲 null,否則拋出NullPointerException。
返回值:
此哈希表中指定key以前的值;如果不存在該key,則返回 null
代碼簡單分析:
public synchronized V put(K key, V value) {
// 校驗映射的值value不能爲null
if (value == null) {
throw new NullPointerException();
}
// 校驗key是否已在Hashtable中
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//計算key在Hashtable中位置,和整型的最大值做位與運算,保證得到一個正整數
//對entry[]長度取模運算,得到key在數組中的位置
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
//如果數組中的位置已被佔用,遍歷該位置的entry鏈表,查看該key是否已存在
//如果已存在,更新值,返回舊值
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//該key不存在,新建entry對象加入Hashtable,返回null
addEntry(hash, key, value, index);
return null;
}
再進入 addEntry(hash, key, value, index) 方法中看一下:
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
//檢驗數組長度是否達到容量閥值
if (count >= threshold) {
// 如果超過閾值,rehash過程
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
//創建新的entry,頭插法添加鏈表節點,tab[index]位置處放的是新加入的key對應的Entry對象.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
最後看一下 rehash() 過程:
/**
* 增加此哈希表的容量並在內部重新組織該哈希表
* 以便更有效地容納和訪問其哈希表
* 當哈希表中的鍵數超過該哈希表的容量和負載因子的乘積時,將自動調用此方法。
*/
@SuppressWarnings("unchecked")
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
//新的容量變爲舊容量的2倍 + 1
//Entry[]最大容量的限制,整型最大值減8
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++;
//更新下一次觸發rehash的閥值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
//重新映射hashtable中已有的數據
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.3 get 方法
public synchronized V get(Object key)
返回指定鍵key所映射到的值,如果此映射不包含此key的映射,則返回 null
. 注意get()方法也是被synchronized修飾。
更確切地講,如果此映射包含滿足 (key.equals(k))
的從鍵 k
到值 v
的映射,則此方法返回 v
;否則,返回 null
。
入參:key不能爲null,否則拋出 NullPointerException
代碼很簡單:
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
//找到tab[index],遍歷entry鏈表對比查找
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
2.1.4 containsKey方法
public synchronized boolean containsKey(Object key)
測試指定對象是否爲此哈希表中的鍵key
源代碼很簡單,和上面 get() 方法幾乎一樣:
public synchronized boolean containsKey(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 true;
}
}
return false;
}
2.1.4 containsValue 和 contains 方法
public boolean containsValue(Object value)
public synchronized boolean contains(Object value)
這兩個方法基本功能一致,都是測試此映射表中是否存在與指定值value關聯的鍵;其中containsValue方法內部直接調用的contains方法實現,所以方法定義沒有加synchronized。
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
//兩層循環,第一層遍歷entry數組,第二層遍歷entry鏈表
for (int i = tab.length ; i-- > 0 ;) {
for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
2.1.5 remove 方法
public synchronized V remove(Object key)
從哈希表中移除該鍵及其相應的值。如果該鍵不在哈希表中,則此方法不執行任何操作。
入參:key不能爲null,否則拋出 NullPointerException
返回值:此哈希表中與該鍵key存在映射關係的值;如果該鍵沒有映射關係,則返回 null
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
//刪除key操作就是考慮鏈表節點的刪除,prev節點記錄key的前置節點
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()、keySet()、entrySet()等,大家可以自己看一下,這裏不再介紹了。