容器源码分析之HashTable(八)
1.HashTable的類聲明
public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>, Cloneable, java.io.Serializable繼承的是Dictionary類,實現了Map、Cloneable、Serializable。
注意:這也是Hashtable和HashMap第一個不同的地方,HashMap實現的是AbstractMap類。
2. HashTable的成員變量
1)private transient Entry< ?,? >[] table;//用來實現Hashtable所借助的數組,默認大小為11。
注意:這是Hashtable與HashMap第二個不同的地方,Hashtable的默認容量大小為11,HashMap的默認容量大小為16且擴容的大小必須一直是2的冪次方。
2)、private transient int count;//用來記錄table數組中存儲元素的個數
3)、private int threshold;//擴容的門限,即如果數組table中存儲的元素個數大于threshold,則擴大數組table的大小
4)、private float loadFactor;//加載因子,默認值為0.75f,擴容門線threshold的值等于loadFactor與數組table的容量的乘積.
3. HashTable的構造方法
/**我們獲得的hashtable對象,無論有參數,無參數的 最終都調用的這個構造*initialCapacity為初始容量,默認大小為 11*loadFactor為加載因子,默認大小為0.75f,好比是你的內容超過了 總長度了75%,就會自動擴容 */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];//擴容門限threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); }4.HashMap的重要方法
1.put方法
put方法存儲數據的思想如下:首先根據拿到key的hashcode,然后根據hashCode找到此元素即將要存儲的位置index.如果位置index已經有元素鏈表了,則在此鏈表中,尋找是否有key已經存在了,如果存在,則更新value值,如果不存在,則將此value存儲在index位置上。
從源碼中可以看到,put方法首先檢查value是否為null,如果為空,則拋異常。
注意:這也是Hashtable與HashMap的另一個不同點,在HashMap中,null可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值為null。
另一個區別,在Hashtable中所有的方法都是用synchronized關鍵字進行了同步,而HashMap中的方法在缺省情況下是非同步的。在多線程并發的環境下,可以直接使用Hashtable,但是要使用HashMap的話就要自己增加同步處理了。
還有區別就是HashMap的teble有可能是treenode結構有可能是列表,而HashTable都是對象列表,所以HashTable處理的相對簡單。
public synchronized V put(K key, V value) {// Make sure the value is not nullif (value == null) {throw new NullPointerException();}// Makes sure the key is not already in the hashtable.Entry<?,?> tab[] = table;int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> entry = (Entry<K,V>)tab[index];//如果數組table中有值,則檢查下是否有此key,如果有,則更新valuefor(; 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;}addEntry方法
思想如下:首先檢查下數組table存儲的元素個數是否大于擴容門限threshold.如果大于,則擴容,擴容之后,重新獲取key的hashcode,并根據hashcode重新計算要存儲的位置index.最后將要存儲的數據存儲到table[index]中。這里要注意的一點是,如果table[index]中已經有其它元素了,那么在同一個位子上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。
private void addEntry(int hash, K key, V value, int index) {modCount++;Entry<?,?> tab[] = table;//檢查數組table中存儲的數據個數>=門限,如果大于等于,則對數組進行擴容if (count >= threshold) {// Rehash the table if the threshold is exceededrehash();//擴容之后,對key的hashcode、以及即將要存儲的位置index重新進行更新tab = table;hash = key.hashCode();index = (hash & 0x7FFFFFFF) % tab.length;}// Creates the new entry.@SuppressWarnings("unchecked")//獲取table數組在index的元素,如果非空,則新節點放在這個節點的前面,成為新的鏈表頭Entry<K,V> e = (Entry<K,V>) tab[index];tab[index] = new Entry<>(hash, key, value, e);count++;}2、refresh方法
rehash方法的實現思想如下:首先對原來的長度乘以2+1就為即將擴容的長度。然后新建一個newCapacity的數組,將原來的table數組的元素拷貝到新的數組中去即可。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;@SuppressWarnings("unchecked")protected void rehash() {int oldCapacity = table.length;Entry<?,?>[] oldMap = table;// overflow-conscious codeint newCapacity = (oldCapacity << 1) + 1;if (newCapacity - MAX_ARRAY_SIZE > 0) {if (oldCapacity == MAX_ARRAY_SIZE)// Keep running with MAX_ARRAY_SIZE bucketsreturn;newCapacity = MAX_ARRAY_SIZE;}Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];modCount++;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;}}}3、get方法
get方法的思想也比較簡單,如下:首先獲取key的hashcode,然后根據hashcode得到存儲位置index,最后在table[index]取出元素即可,不過要注意的是,這里可能存儲了多個元素構成了一個鏈表,因此要進行一個key和hash的判斷。
@SuppressWarnings("unchecked")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;}**HashMap和Hashtable的幾點區別**
1、繼承類不一樣
HashMap繼承的是AbstractMap,Hashtable繼承的是Dictionary。實現的接口一致(Map、Cloneable和Serializable)
2、初始容量不一樣
HashMap默認容量為16,且容量只能是2的冪次方;Hashtable默認容量為11,容量并沒有2的冪次方的限制,增加的方式是 oldCap*2+1。
3、HashMap是線程不安全的,Hashtable是線程安全的
默認情況下,HashMap類中的方法并沒有進行同步,而Hashtable中的方法均使用synchronized進行了同步。因此,在多線程并發時,Hashtable可以直接使用,HashMap需要我們加入額外的同步操作。
4、使用的hashcode不一樣
Hashtable是直接使用的key的hashcode(key.hashcode())。而HashMap的key的hashcode是另外計算的。hashMap 獨立了hash算法,并且算法是通過key value 多次算出來的,減少了重復性
5、HashMap允許有一個key為null,多個value為null。而Hashtable不允許key和value為null。
6、HashMap和Hashtable內部遍歷方式的實現不一樣
Hashtable、HashMap都使用了 Iterator。而由于歷史原因,Hashtable還使用了Enumeration的方式 。
總結
以上是生活随笔為你收集整理的容器源码分析之HashTable(八)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 容器源码解析之HashMap(七)
- 下一篇: 容器源码解析之LinkedHashMap