概述
??Hashtable是一個(gè)比較古老的Map實(shí)現(xiàn)類(lèi)藏雏,從它的名稱(chēng)就可以看得出來(lái)粪摘,因?yàn)闆](méi)有遵循Java的語(yǔ)言規(guī)范击喂。它和HashMap很像枯芬,同屬于散列表论笔,有以下特性:
- 首先就是線(xiàn)程安全,這也估計(jì)算是唯一一個(gè)優(yōu)于HashMap的特性了吧千所;
- Hashtable不允許key或者value為null狂魔;
- 自從JDK1.2開(kāi)始,Hashtable實(shí)現(xiàn)了Map接口淫痰,成為了Map容器中的一員最楷。看樣子最開(kāi)始是不屬于Map容器的。
- 不建議使用管嬉,以后說(shuō)不定哪天就廢掉了皂林。連官方文檔也說(shuō)了,如果在非線(xiàn)程安全的情況下使用蚯撩,建議使用HashMap替換础倍,如果在線(xiàn)程安全的情況下使用,建議使用ConcurrentHashMap替換胎挎。
??但是沟启,畢竟作為Map結(jié)構(gòu)的一員,還是大致分析一下它的源碼犹菇,然后分析一下不建議使用的原因德迹。
屬性
public class Hashtable<K,V> extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
// Hashtable保存數(shù)據(jù)的數(shù)組
private transient Entry<?,?>[] table;
// hashtable的容量
private transient int count;
// 閾值
private int threshold;
// 負(fù)載因子
private float loadFactor;
// 結(jié)構(gòu)性修改
private transient int modCount = 0;
}
??Hashtable是繼承自古老的Dictionary類(lèi),而Dictionary類(lèi)揭芍,顧名思義胳搞,就是字典類(lèi),算是早期的Map称杨,不過(guò)該類(lèi)基本上已經(jīng)廢棄了肌毅。為什么廢棄呢,大致看下Dictionary的源碼就知道了姑原。除了常規(guī)的get悬而,put請(qǐng)求外,還提供了一些遍歷的方法锭汛,返回的是Enumeration類(lèi)型笨奠。而Enumeration接口其實(shí)算是被Iterator替換了,因?yàn)镮terator提供的功能更多唤殴,更方便般婆。所以,就目前而言眨八,Dictionary存在的意義恐怕就只是為了兼容原來(lái)繼承它的一些類(lèi)了吧腺兴。
方法
- Hashtable底層是通過(guò)數(shù)組加鏈表來(lái)實(shí)現(xiàn)的。
- Hashtable并沒(méi)有太多的常量廉侧,比如默認(rèn)容量大小都是直接寫(xiě)在代碼中,而沒(méi)使用常量篓足。
- 從它的構(gòu)造函數(shù)我們可以知道段誊,Hashtable默認(rèn)capacity是11,默認(rèn)負(fù)載因子是0.75.栈拖。
put方法
// put是synchronized方法
public synchronized V put(K key, V value) {
// 首先就是確保value不能為空
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
// 計(jì)算數(shù)組的index
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
// 如果index處已經(jīng)有值连舍,并且通過(guò)比較hash和equals方法之后,如果有相同key的替換涩哟,返回舊值
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
// 添加數(shù)組
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
// 如果容量大于了閾值索赏,擴(kuò)容
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];
// 在數(shù)組索引index位置保存
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
- 通過(guò)
tab[index] = new Entry<>(hash, key, value, e);
這一行代碼盼玄,并且根據(jù)Entry的構(gòu)造方法,我們可以知道潜腻,Hashtable是在鏈表的頭部添加元素的埃儿,而HashMap是尾部添加的,這點(diǎn)可以注意下融涣。- Hashtable計(jì)算數(shù)組index的方式和HashMap有點(diǎn)不同童番,
int index = (hash & 0x7FFFFFFF) % tab.length;
0x7FFFFFFF也就是Integer.MAX_VALUE,也就是2的32次方-1威鹿,二進(jìn)制的話(huà)也就是11111111...剃斧,那么(hash&0x7FFFFFFF)的含義看來(lái)看去好像只有對(duì)符號(hào)位有效了,就是負(fù)數(shù)的時(shí)候忽你,應(yīng)該是為了過(guò)濾負(fù)數(shù)幼东,而后面的取模就很簡(jiǎn)單了,把index的取值限制在數(shù)組的長(zhǎng)度之內(nèi)科雳。
rehash方法
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// 擴(kuò)容為原來(lái)的2倍加1
int newCapacity = (oldCapacity << 1) + 1;
// 擴(kuò)容后的數(shù)量校驗(yàn)
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
// 新數(shù)組
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
// 閾值計(jì)算
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
// 雙層循環(huán)根蟹,將原數(shù)組中數(shù)據(jù)復(fù)制到新數(shù)組中
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;
// 重新根據(jù)hash計(jì)算index
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
get方法
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
// 計(jì)算數(shù)組index
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;
}
總結(jié)
- 首先,Hashtable底層是通過(guò)數(shù)組加鏈表實(shí)現(xiàn)的炸渡,這點(diǎn)和JDK1.8之前的HashMap差不多娜亿。
- 其次,Hashtable是不允許key或者value為null的蚌堵。
- 并且买决,Hashtable的計(jì)算索引方法,默認(rèn)容量大小吼畏,擴(kuò)容方法都與HashMap不太一樣督赤。
- 其實(shí)我們可以看到,Hashtable之所以線(xiàn)程安全泻蚊,大部分方法都是使用了synchronized關(guān)鍵字躲舌,雖然JDK優(yōu)化了synchronized,但在方法上使用該關(guān)鍵字性雄,無(wú)疑仍舊是效率低下的操作没卸。就這方面來(lái)說(shuō),ConcurrentHashMap無(wú)疑比Hashtable好多了秒旋,后續(xù)會(huì)有專(zhuān)門(mén)文章介紹ConcurrentHashMap约计,這里就不多說(shuō)了。
??總之呢迁筛,Hashtable無(wú)疑算是廢掉了煤蚌,說(shuō)不定過(guò)不了多久,它就消失在Map框架中了呢。