文章目錄
一、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