一、一些變量介紹
//存儲鍵值對的數組
private transient Entry<K,V>[] table;
//鍵值對總數
private transient int count;
//容量的閾值,超過此容量將會擴容。
private int threshold;
//負載因子
private float loadFactor;
//hashtable被改變的次數,用於快速失敗機制
private transient int modCount = 0;
二、構造函數
1.
//無參默認構造函數,默認容量大小爲11,負載比(負載因子)爲0.75
public Hashtable() {
this(11, 0.75f);
}
2.
//支持自定義容量大小
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
3.
//支持自定義容量大小和負載比
public Hashtable(int initialCapacity, float loadFactor) {
//容量不能小於0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//負載因子必須大於0
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
//如果容量傳入的爲0,則調整爲1
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
//初始化數組大小
table = new Entry<?,?>[initialCapacity];
//閾值在正常計算值和最大允許值之間取最小值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
4.
//允許傳入map結構的參數
public Hashtable(Map<? extends K, ? extends V> t) {
//容量取傳入參數的兩倍,如果值小於11則取11
this(Math.max(2*t.size(), 11), 0.75f);
//將數據存入到table中
putAll(t);
}
//同步存入到table中,保證線程安全
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());
}
三、存儲數據--put
//同步存儲數據
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {//如果存儲的value爲null,則拋出異常
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//hash & 0x7FFFFFFF是爲了保證得到的數是正數
int index = (hash & 0x7FFFFFFF) % tab.length;//取模獲取存儲的下標位置
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
//如果有key值相同的數據,則更新爲最新值,同時返回老的值
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
//如果沒有key值相同的數據,則執行添加操作
addEntry(hash, key, value, index);
return null;
}
//添加元素
private void addEntry(int hash, K key, V value, int index) {
modCount++;//修改次數加1
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);//把最新的元素放到該數組位置,構造函數可把原來的元素e放到最新構建的元素後面,從而形成鏈表
count++;
}
//元素構造函數
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
四、擴容rehash()
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
//每次擴容爲原來的2倍加1
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++;//屬於結構變化,修改次數+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;//當前要操作的元素e
old = old.next;//爲下次循環做準備
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];//獲取該位置原來的元素,並將新元素的next屬性指向他
newMap[index] = e;//賦值爲新元素
}
}
}
五、get方法
public synchronized V get(Object key) {//根據鍵取出對應索引
Entry tab[] = table;
int hash = hash(key);//先根據key計算hash值
int index = (hash & 0x7FFFFFFF) % tab.length;//再根據hash值找到索引
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {//遍歷entry鏈
if ((e.hash == hash) && e.key.equals(key)) {//若找到該鍵
return e.value;//返回對應的值
}
}
return null;//否則返回null
}
六、remove方法
public synchronized V remove(Object key) {//刪除指定鍵值
Entry tab[] = table;
int hash = hash(key);//計算該鍵hash值
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;
}
七、clear方法
public synchronized void clear() {
Entry<?,?> tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;//直接將每個數組置空
count = 0;
}
八、與HashMap比較
- Hashtable是線程安全,而HashMap非線程安全
- Hashtable的key和value都不可以)爲null值;HashMap允許得key和value都可以爲null值
- Hashtable使用Enumeration進行遍歷;HashMap使用Iterator進行遍歷
- Hashtable在鏈表頭部插入,HashMap(jdk8之後)在尾部插入
- 存儲位置計算方式不同,HashMap用與代替求模
- 哈希值的使用不同,HashTable直接使用對象的hashCode; HashMap重新計算hash值
- Hashtable每次擴容,新容量爲舊容量的2倍加1,而HashMap爲舊容量的2倍
- Hashtable默認容量爲 11,HashMap默認容量爲16;Hashtable的容量爲任意正數(最小爲1),而HashMap的容量始終爲2的n次方.
- Hashtable和HashMap都是通過鏈表解決衝突
- Hashtable和HashMap默認負載因子都爲0.75