HashTable原理和底層實現
阿新 • • 發佈:2018-12-16
1. 概述
上次討論了HashMap的結構,原理和實現,本文來對Map家族的另外一個常用集合HashTable進行介紹。HashTable和HashMap兩種集合非常相似,經常被各種面試官問到兩者的區別。
對於兩者的區別,主要有以下幾點:
- HashMap是非同步的,沒有對讀寫等操作進行鎖保護,所以是執行緒不安全的,在多執行緒場景下會出現資料不一致的問題。而HashTable是同步的,所有的讀寫等操作都進行了鎖(
synchronized
)保護,在多執行緒環境下沒有安全問題。但是鎖保護也是有代價的,會對讀寫的效率產生較大影響。 - HashMap結構中,是允許儲存
null
的,Entry.key
Entry.value
均可以為null
。但是HashTable中是不允許儲存null
的。 - HashMap的迭代器(
Iterator
)是fail-fast迭代器,但是Hashtable的迭代器(enumerator
)不是fail-fast的。如果有其它執行緒對HashMap進行的新增/刪除元素,將會丟擲ConcurrentModificationException
,但迭代器本身的remove
方法移除元素則不會丟擲異常。這條同樣也是Enumeration和Iterator的區別。 2. 原理HashTable類中,儲存實際資料的,依然是
Entry
物件。其資料結構與HashMap是相同的。 HashTable類繼承自Dictionary
Map
,Cloneable
和java.io.Serializable
,如下圖所示。
HashTable中的主要方法,如put
,get
,remove
和rehash
等,與HashMap中的功能相同,這裡不作贅述,可以參考另外一篇文章HashMap原理和底層實現
3. 原始碼分析
HashTable的主要方法的原始碼實現邏輯,與HashMap中非常相似,有一點重大區別就是所有的操作都是通過synchronized
鎖保護的。只有獲得了對應的鎖,才能進行後續的讀寫等操作。
1. put方法
put方法的主要邏輯如下:
- 先獲取
synchronized
鎖。 - put方法不允許
null
null
,則直接丟擲異常。 - 計算
key
的雜湊值和index - 遍歷對應位置的連結串列,如果發現已經存在相同的hash和key,則更新value,並返回舊值。
- 如果不存在相同的key的Entry節點,則呼叫
addEntry
方法增加節點。 addEntry
方法中,如果需要則進行擴容,之後新增新節點到連結串列頭部。
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;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
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++;
}
2. get方法
get方法的主要邏輯如下
- 先獲取
synchronized
鎖。 - 計算key的雜湊值和index。
- 在對應位置的連結串列中尋找具有相同hash和key的節點,返回節點的value。
- 如果遍歷結束都沒有找到節點,則返回
null
。
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;
}
3.rehash擴容方法
rehash擴容方法主要邏輯如下:
- 陣列長度增加一倍(如果超過上限,則設定成上限值)。
- 更新雜湊表的擴容門限值。
- 遍歷舊錶中的節點,計算在新表中的index,插入到對應位置連結串列的頭部。
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
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++;
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;
}
}
}
4.remove方法
remove方法主要邏輯如下:
- 先獲取synchronized鎖。
- 計算key的雜湊值和index。
- 遍歷對應位置的連結串列,尋找待刪除節點,如果存在,用
e
表示待刪除節點,pre
表示前驅節點。如果不存在,返回null
。 - 更新前驅節點的
next
,指向e
的next。返回待刪除節點的value值。
4. 總結
HashTable相對於HashMap的最大特點就是執行緒安全,所有的操作都是被synchronized
鎖保護的
作者:道可 連結:https://www.imooc.com/article/details/id/23015 來源:慕課網 本文原創釋出於慕課網 ,轉載請註明出處,謝謝合作