和HashMap同樣,Hashtable 也是一個散列表,它存儲的內容是鍵值對(key-value)映射,它在很大程度上和HashMap的實現差很少。html
Hashtable 的函數都是同步的,這意味着它是線程安全的。它的key、value都不能夠爲null。此外,Hashtable中的映射不是有序的。java
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
HashTable繼承Dictionary類,實現Map接口。其中Dictionary類是任何可將鍵映射到相應值的類的抽象父類。每一個鍵和每一個值都是一個對象。在任何一個 Dictionary 對象中,每一個鍵至多與一個值相關聯。Map是」key-value鍵值對」接口。數組
1 private transient Entry<?,?>[] table;//table是一個Entry[]數組類型,而Entry實際上就是一個單向鏈表。 2 private transient int count;//count是Hashtable的大小,它是Hashtable保存的鍵值對的數量。 3 private int threshold;//threshold是Hashtable的閾值,用於判斷是否須要調整Hashtable的容量。threshold的值="容量*加載因子"。 4 private float loadFactor;//loadFactor就是加載因子。 5 private transient int modCount = 0;//modCount是用來實現fail-fast機制的
所謂快速失敗就是在併發集合中,其進行迭代操做時,如有其餘線程對其進行結構性的修改,這時迭代器會立馬感知到,而且當即拋出ConcurrentModificationException異常,而不是等到迭代完成以後才告訴你(你已經出錯了)。安全
1 // 默認構造函數。 2 public Hashtable() { 3 this(11, 0.75f); 4 } 5 6 // 指定「容量大小」的構造函數 7 public Hashtable(int initialCapacity) { 8 this(initialCapacity, 0.75f); 9 } 10 11 // 指定「容量大小」和「加載因子」的構造函數 12 public Hashtable(int initialCapacity, float loadFactor) { 13 if (initialCapacity < 0)//驗證初始容量 14 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); 15 if (loadFactor <= 0 || Float.isNaN(loadFactor))//驗證加載因子 16 throw new IllegalArgumentException("Illegal Load: "+loadFactor); 17 if (initialCapacity==0) 18 initialCapacity = 1; 19 this.loadFactor = loadFactor; 20 table = new Entry[initialCapacity];//初始化table,得到大小爲initialCapacity的table數組 21 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//計算閥值 22 initHashSeedAsNeeded(initialCapacity);//初始化HashSeed值 23 } 24 25 // 包含「子Map」的構造函數 26 public Hashtable(Map<? extends K, ? extends V> t) { 27 this(Math.max(2*t.size(), 11), 0.75f);//設置table容器大小,其值==t.size * 2 + 1 28 putAll(t); 29 }
將指定 key 映射到此哈希表中的指定 value。注意這裏鍵key和值value都不可爲空。數據結構
1 public synchronized V put(K key, V value) { 2 if (value == null) {// 確保value不爲null 3 throw new NullPointerException(); 4 } 5 6 /* 7 * 確保key在table[]是不重複的 8 * 處理過程: 9 * 一、計算key的hash值,確認在table[]中的索引位置 10 * 二、迭代index索引位置,若是該位置處的鏈表中存在一個同樣的key,則替換其value,返回舊值 11 */ 12 Entry tab[] = table; 13 int hash = hash(key); //計算key的hash值 14 int index = (hash & 0x7FFFFFFF) % tab.length; //確認該key的索引位置 15 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { //迭代,尋找該key,替換 16 if ((e.hash == hash) && e.key.equals(key)) { 17 V old = e.value; 18 e.value = value; 19 return old; 20 } 21 } 22 23 modCount++; 24 if (count >= threshold) { //若是容器中的元素數量已經達到閥值,則進行擴容操做 25 rehash(); 26 tab = table; 27 hash = hash(key); 28 index = (hash & 0x7FFFFFFF) % tab.length; 29 } 30 31 Entry<K,V> e = tab[index];// 在索引位置處插入一個新的節點 32 tab[index] = new Entry<>(hash, key, value, e); 33 count++;//容器中元素+1 34 return null; 35 }
put方法的整個處理流程是:計算key的hash值,根據hash值得到key在table數組中的索引位置,而後迭代該key處的Entry鏈表(咱們暫且理解爲鏈表),若該鏈表中存在一個這個的key對象,那麼就直接替換其value值便可,不然在將改key-value節點插入該index索引位置處。多線程
過程演示以下:併發
首先咱們假設一個容量爲5的table,存在以下的鍵值對:框架
而後咱們插入一個數:put(16,22),key=16在table的索引位置爲1,同時在1索引位置有兩個數,程序對該「鏈表」進行迭代,發現存在一個key=16,這時要作的工做就是用newValue=22替換oldValue16,並將oldValue=16返回。函數
在put(33,33),key=33所在的索引位置爲3,而且在該鏈表中也沒有存在某個key=33的節點,因此就將該節點插入該鏈表的第一個位置。性能
擴容操做:在put方法中,若是須要向table[]中添加Entry元素,會首先進行容量校驗,若是容量已經達到了閥值,HashTable就會進行rehash()擴容處理
1 protected void rehash() { 2 int oldCapacity = table.length; 3 Entry<K,V>[] oldMap = table; 4 5 int newCapacity = (oldCapacity << 1) + 1;//新容量=舊容量 * 2 + 1 6 if (newCapacity - MAX_ARRAY_SIZE > 0) { 7 if (oldCapacity == MAX_ARRAY_SIZE) 8 return; 9 newCapacity = MAX_ARRAY_SIZE; 10 } 11 Entry<K,V>[] newMap = new Entry[];//新建一個size = newCapacity 的HashTable 12 modCount++; 13 threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//從新計算閥值 14 boolean rehash = initHashSeedAsNeeded(newCapacity);//從新計算hashSeed 15 table = newMap; 16 for (int i = oldCapacity ; i-- > 0 ;) {//將原來的元素拷貝到新的HashTable中 17 for (Entry<K,V> old = oldMap[i] ; old != null ; ) { 18 Entry<K,V> e = old; 19 old = old.next; 20 if (rehash) { 21 e.hash = hash(e.key); 22 } 23 int index = (e.hash & 0x7FFFFFFF) % newCapacity; 24 e.next = newMap[index]; 25 newMap[index] = e; 26 } 27 } 28 }
經過上面rehash代碼咱們能夠看到容量擴大兩倍+1,同時須要將原來HashTable中的元素一一複製到新的HashTable中,這個過程是比較消耗時間的,同時還須要從新計算hashSeed的,畢竟容量已經變了。
關於閥值:好比初始值十一、加載因子默認0.75,那麼這個時候閥值threshold=8,當容器中的元素達到8時,HashTable進行一次擴容操做,容量 = 8 * 2 + 1 =17,而閥值threshold=17*0.75 = 13,當容器元素再一次達到閥值時,HashTable還會進行擴容操做,一次類推。
相對於put方法,get方法就會比較簡單,處理過程就是計算key的hash值,判斷在table數組中的索引位置,而後迭代鏈表,匹配直到找到相對應key的value,若沒有找到返回null。
1 public synchronized V get(Object key) { 2 Entry tab[] = table; 3 int hash = hash(key); 4 int index = (hash & 0x7FFFFFFF) % tab.length; 5 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { 6 if ((e.hash == hash) && e.key.equals(key)) { 7 return e.value; 8 } 9 } 10 return null; 11 }
第一步:根據entrySet()獲取Hashtable的「鍵值對」的Set集合。
第二步:經過Iterator迭代器遍歷「第一步」獲得的集合。
1 // 假設table是Hashtable對象 2 // table中的key是String類型,value是Integer類型 3 Integer integ = null; 4 Iterator iter = table.entrySet().iterator(); 5 while(iter.hasNext()) { 6 Map.Entry entry = (Map.Entry)iter.next(); 7 // 獲取key 8 key = (String)entry.getKey(); 9 // 獲取value 10 integ = (Integer)entry.getValue(); 11 }
第一步:根據keySet()獲取Hashtable的「鍵」的Set集合。
第二步:經過Iterator迭代器遍歷「第一步」獲得的集合。
1 // 假設table是Hashtable對象 2 // table中的key是String類型,value是Integer類型 3 String key = null; 4 Integer integ = null; 5 Iterator iter = table.keySet().iterator(); 6 while (iter.hasNext()) { 7 // 獲取key 8 key = (String)iter.next(); 9 // 根據key,獲取value 10 integ = (Integer)table.get(key); 11 }
第一步:根據value()獲取Hashtable的「值」的集合。
第二步:經過Iterator迭代器遍歷「第一步」獲得的集合。
1 // 假設table是Hashtable對象 2 // table中的key是String類型,value是Integer類型 3 Integer value = null; 4 Collection c = table.values(); 5 Iterator iter= c.iterator(); 6 while (iter.hasNext()) { 7 value = (Integer)iter.next(); 8 }
第一步:根據keys()獲取Hashtable的集合。
第二步:經過Enumeration遍歷「第一步」獲得的集合。
Enumeration enu = table.keys(); while(enu.hasMoreElements()) { System.out.println(enu.nextElement()); }
第一步:根據elements()獲取Hashtable的集合。
第二步:經過Enumeration遍歷「第一步」獲得的集合。
Enumeration enu = table.elements(); while(enu.hasMoreElements()) { System.out.println(enu.nextElement()); }
HashTable的應用很是普遍,HashMap是新框架中用來代替HashTable的類,也就是說建議使用HashMap。
下面着重比較一下兩者的區別:
Hashtable是基於陳舊的Dictionary類的,HashMap是java 1.2引進的Map接口的一個實現。
Hashtable 中的方法是同步的,保證了Hashtable中的對象是線程安全的。
HashMap中的方法在缺省狀況下是非同步的,HashMap中的對象並非線程安全的。在多線程併發的環境下,能夠直接使用Hashtable,可是要使用HashMap的話就要本身增長同步處理了。
單線程中, HashMap的效率大於Hashtable。由於同步的要求會影響執行的效率,因此若是你不須要線程安全的集合,HashMap是Hashtable的輕量級實現,這樣能夠避免因爲同步帶來的沒必要要的性能開銷,從而提升效率。
Hashtable中,key和value都不容許出現null值,不然出現NullPointerException。
在HashMap中,null能夠做爲鍵,這樣的鍵只有一個;能夠有一個或多個鍵所對應的值爲null。當get()方法返回null值時,便可以表示 HashMap中沒有該鍵,也能夠表示該鍵所對應的值爲null。所以,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵,而應該用containsKey()方法來判斷。
Hashtable、HashMap都使用了 Iterator。而因爲歷史緣由,Hashtable還使用了Enumeration的方式。
Hashtable和HashMap它們兩個內部實現方式的數組的初始大小和擴容的方式。
HashTable中hash數組默認大小是11,增長的方式是 old*2+1。
HashMap中hash數組的默認大小是16,並且必定是2的指數。
不管何時有多個線程訪問相同實例的可能時,就應該使用Hashtable,反之使用HashMap。非線程安全的數據結構能帶來更好的性能。
若是在未來有一種可能—你須要按順序得到鍵值對的方案時,HashMap是一個很好的選擇,由於有HashMap的一個子類 LinkedHashMap。因此若是你想可預測的按順序迭代(默認按插入的順序),你能夠很方便用LinkedHashMap替換HashMap。反觀要是使用的Hashtable就沒那麼簡單了。同時若是有多個線程訪問HashMap,Collections.synchronizedMap()能夠代替,總的來講HashMap更靈活。
http://blog.csdn.net/zheng0518/article/details/42199477
http://www.cnblogs.com/devinzhang/archive/2012/01/13/2321481.html