在進行Hashtable源碼解析以前,我先扔出Hashtable與HashMap有哪些區別?
1.關於null,HashMap容許key和value均可覺得null,而Hashtable則不接受key爲null或value爲null的鍵值對。
2.關於線程安全,HashMap是線程不安全的,Hashtable是線程安全的,由於Hashtable的許多操做函數都用synchronized修飾。
3.Hashtable與HashMap實現的接口一致,但Hashtable繼承Dictionary,而HashMap繼承自AbstractMap,即父類不一樣。
4.默認初始容量不一樣,擴容大小不一樣。HashMap的hash數組的默認大小是16,並且必定是2 的指數,增長方式old2;Hashtable中hash數組默認大小是11,增長的方式是old2+1。java
以前簡要分析過HashMap的代碼,關於HashMap可點擊:Java集合之HashMap源碼解析 .下面咱們開始進行Hashtable的分析,先看代碼清單1:android
public class HashtableTest {
public static void main(String [] args){
Hashtable<String, String> table=new Hashtable<>();
Hashtable<String, String> table1=new Hashtable<>(16);
Hashtable<String, String> table2=new Hashtable<>(16, 0.75f);
HashMap<String,String> map=new HashMap<>();
Hashtable<String,String> table3=new Hashtable<>(map);
table.put("T1", "1");
table.put("T2", "2");
table.put(null, "3");
System.out.println();
System.out.println(table.toString());
}
}複製代碼
咱們看到在建立對象上,和HashMap的使用方式類似,咱們繼續看其內部的構造函數是怎麼樣的,如代碼清單2: 算法
private transient Entry<?,?>[] table;//數組
private transient int count;//鍵值對的數量
private int threshold;//閥值
private float loadFactor;//加載因子
private transient int modCount = 0;//修改次數
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);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];//這裏是與HashMap的區別之一,HashMap中table
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
initHashSeedAsNeeded(initialCapacity);
}
public Hashtable(int initialCapacity) {//指定初始數組長度
this(initialCapacity, 0.75f);
}
public Hashtable() {//從這裏能夠看出容量的默認值爲16,加載因子爲0.75f.
this(11, 0.75f);
}
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}複製代碼
Hashtable的構造函數主要對table數組進行了初始化,這裏沒有什麼要拿出來詳細講的,咱們仍是看下put的流程,代碼清單3:數組
public synchronized V put(K key, V value) {//這裏方法修飾符爲synchronized,因此是線程安全的。
if (value == null) {
throw new NullPointerException();//value若是爲Null,拋出異常
}
Entry tab[] = table;
int hash = hash(key);//hash裏面的代碼是hashSeed^key.hashcode(),null.hashCode()會拋出異常,因此這就解釋了Hashtable的key和value不能爲null的緣由。
int index = (hash & 0x7FFFFFFF) % tab.length;//獲取數組元素下標,先對hash值取正,而後取餘。
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;//修改次數。
if (count >= threshold) {//鍵值對的總數大於其閥值
rehash();//在rehash裏進行擴容處理
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
private int hash(Object k) {
// hashSeed will be zero if alternative hashing is disabled.
return hashSeed ^ k.hashCode();//在1.8的版本中,hash就直接爲k.hashCode了。
}
protected void rehash() {
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table;
int newCapacity = (oldCapacity << 1) + 1;//擴容,若是默認值是11,則擴容以後,數組的長度爲23
if (newCapacity - MAX_ARRAY_SIZE > 0) {//這裏的最大值和HashMap裏的最大值不一樣,這裏Max_ARRAY_SIZE的是由於有些虛擬機實現會限制數組的最大長度。
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<K,V>[] newMap = new Entry[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
boolean rehash = initHashSeedAsNeeded(newCapacity);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {//遷移鍵值對
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}複製代碼
經過代碼清單2和3,咱們能夠看到Hashtable的數據結構和HashMap同樣是一個Entry的數組,數組元素是一個單鏈表,這裏展現出了一個很明顯的區別,即hash值沒有擾動處理。那怎麼解決衝突呢?Hashtable的索引求值公式實際上是:
((hashSeed^k.hashCode())&0x7FFFFFFF)%newCapacity——> hash&0x7FFFFFFF%newCapacity。hash&0x7FFFFFF是爲了保證正數,由於hashCode的值有可能爲負值,這樣說有可能會有同窗說,取正數直接用Math.abs不就好了。。。可是你肯定全部狀況下,abs都能保證輸出是正數嗎?來來舉個例子給你看看:
安全
哈哈,你還能說什麼。hash&0x7FFFFFFF是爲了不負值的出現,對newCapacity求餘是爲了使index在數組索引範圍以內。看到這估計就有人問了那麼HashMap中的hash&(tab.leng-1)怎麼解釋呢?若是不太明白,還請你們仔細看上篇文章,這裏簡單說一下,hash&(tab.length-1)實際上是對(hash&0x7FFFFFFF)%newCapacity的代碼級優化,簡而言之就是位運算的性能是優於求餘運算的。bash
細心的讀者可能會發現HashMap與Hashtable的最大值是不同的,上篇文章咱們說過HashMap的長度是2的指數值,而HashMap的數組的最大長度是:1<<30=1073741824,這個值是int範圍內2的指數值的最大值,不信能夠打印1<<31=-2147483648。關於Hashtable的數組的最大值源碼註釋中有說明,是指虛擬機實現對array的長度有限制,若是你們糾結於這個最大值,why the max value is Integer.MAX_SIZE-8,請參考下面兩個連接:數據結構
stackoverflow.com/questions/3…
stackoverflow.com/questions/3…
說完了Hash與最大值的梗,咱們就要來看看線程安全,Hashtable中的主要方法都加了synchronized關鍵字來修飾(沒有被顏色覆蓋的),以下:
多線程
public synchronized V get(Object key) {//沒有什麼特殊性,就是加了一個synchronized,就是根據index來遍歷索引處的單鏈表。
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}複製代碼
鑑於Hashtable是歷史遺留的類,如今不多有人使用它,即便咱們在對線程安全有要求的場景中,也是經過使用ConcurrentHashMap來解決,而不是使用Hashtable 。這裏能夠簡要的說一下緣由:Hashtable使用synchronized來實現線程安全,效率不高,而ConcurrentHashMap採用鎖分段技術來實現線程安全,大大提升了效率。在多線程環境中,當A線程訪問Hashtable的put方法時,其餘線程是不能訪問諸如get,clear這些方法的,可是在ConcurrentHashMap中只要保證A線程與B線程不是持有一個段鎖,是能夠A線程訪問put時其餘線程同時訪問get操做。函數
最後咱們說一下Hashtable的初始容量爲何是11?Hashtable的擴容方式是:old*2+1,初始容量11,第一次擴容爲23,第二次擴容爲47,能夠看到Hashtable的容量確定是奇數,有一些更是爲質數。到這裏就涉及到了哈希算法相關的知識了,這裏就不展開說哈希算法相關的內容了。Hashtable之因此初始容量爲11(質數)和擴容方式保證爲奇數,是爲了散列得更均勻,也就是減小碰撞發生的概率。性能
轉載請註明出處:blog.csdn.net/android_jia…