上一篇文章翻譯了Hashtable源碼頂部注釋,本文將詳細講解Hashtable的各個方法的實現。 Hashtable階層圖
先來看看Hashtable的定義:
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
從中我們可以瞭解到: Hashtable<K,V>:HashMap是以key-value形式儲存資料的 extends Dictionary<K,V>:Dictionary 類是一個抽象類別,用來儲存鍵/值對,作用和Map類相似。 implements Map<K,V>:實現了Map,實現了Map中聲明的操作和default方法。 implements Cloneable:表明其可以調用clone()方法來返回執行個體的field-for-field拷貝。 implements java.io.Serializable:表明該類是可以序列化的。 部分全域變數
下面是部分的全域變數,關於迭代器和枚舉類相關的全域變數,在對應的方法或內部類類旁會給出。
/** * hashtable存放的資料. */private transient Entry<?,?>[] table;/** * hashtable存放的entry的個數. */private transient int count;/** * hashtable進行擴容的臨界值(這個值等於(int)(capacity * loadFactor)) * * @serial */private int threshold;/** * 負載因子. * * @serial */private float loadFactor;/** * hashtable被結構型修改的次數。 */private transient int modCount = 0;
Hashtable不要求底層數組的容量一定要為2的整數次冪,而HashMap則要求一定為2的整數次冪。 建構函式
Hashtable有四個建構函式 Hashtable( int initialCapacity, float loadFactor)
/** * 使用指定參數初始化容量和指定參數負載因子來構造一個空的hashtable. * * @param initialCapacity 指定參數初始化容量 * @param loadFactor 指定參數負載因子 * @exception IllegalArgumentException 如果initialCapacity小於0或者負載因子為非正數。 */public Hashtable(int initialCapacity, float loadFactor) { //如果指定參數初始化容量小於0,拋出異常 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); //如果指定參數負載因子為非正數,拋出異常 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); //初始化hashtable的loadFactor、table、threshold屬性 if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry<?,?>[initialCapacity]; //如果initialCapacity * loadFactor超出了MAX_ARRAY_SIZE,就使用MAX_ARRAY_SIZE 作為threshold threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);}
Hashtable( int initialCapacity)
/** * 使用指定參數初始化容量和預設負載因子(0.75)來構造一個空的hashtable. * * @param initialCapacity 指定參數初始化容量 * @exception IllegalArgumentException 如果initialCapacity小於0 */public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f);}
Hashtable()
/** * 使用預設初始化容量(11)和預設負載因子(0.75)來構造一個空的hashtable. * * 這裡可以看到,Hashtable預設初始化容量為16,而HashMap的預設初始化容量為11。 */public Hashtable() { this(11, 0.75f);}
Hashtable( Map<? extends K, ? extends V> t)
/** * 使用指定的索引值對集合t來構造hashtable。 * * @param t 指定的索引值對集合 * @throws NullPointerException 如果指定的索引值對集合為null * @since 1.2 */public Hashtable(Map<? extends K, ? extends V> t) { //初始hashMap this(Math.max(2*t.size(), 11), 0.75f); //將t中的索引值對插入到hashtable中 putAll(t);}
常用方法
size()
/** * 返回hashtable中key的個數 * * @return 返回hashtable中key的個數 */public synchronized int size() { return count;}
isEmpty()
/** * 判斷hashtable中是否有索引值對映射. * * @return hashtable中沒有索引值對映射,返回true,否則返回false */public synchronized boolean isEmpty() { return count == 0;}
keys()
/** * 返回hashtable中key的枚舉. * * @return 返回hashtable中key的枚舉. * @see Enumeration * @see #elements() * @see #keySet() * @see Map */public synchronized Enumeration<K> keys() { return this.<K>getEnumeration(KEYS);}
elements()
/** * 返回hashtable中value的枚舉. * * @return 返回hashtable中value的枚舉 * @see java.util.Enumeration * @see #keys() * @see #values() * @see Map */public synchronized Enumeration<V> elements() { return this.<V>getEnumeration(VALUES);}
contains( Object value)
/** * 返回hashtable中是否含有指定參數value。 * 這個方法比containsKey方法代價更昂貴 * * @param value 指定參數value * @return hashtable中含有指定參數value * @exception NullPointerException value為null */public synchronized boolean contains(Object value) { if (value == null) { throw new NullPointerException(); } Entry<?,?> tab[] = table; //需要遍曆hashtable的table,代價比較大 for (int i = tab.length ; i-- > 0 ;) { for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) { if (e.value.equals(value)) { return true; } } } return false;}
containsValue( Object value)
/** * 返回hashtable中是否含有指定參數value。 * 這個方法比containsKey方法代價更昂貴 * * @param value 指定參數value * @return hashtable中含有指定參數value * @exception NullPointerException value為null * @since 1.2 */public boolean containsValue(Object value) { return contains(value);}
containsKey( Object key)
/** * Tests if the specified object is a key in this hashtable. * 判斷hashtable是否含有指定參數key * @param key 指定參數key * @return hashtable含有指定參數key,返回true,否則返回false * @throws NullPointerException 如果key為null * @see #contains(Object) */public synchronized boolean containsKey(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); //因為不需要從頭開始遍曆table,所以代價比containsValue要小 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 true; } } return false;}
因為不需要從頭開始遍曆table,所以代價比containsValue要小。
這裡可以看到,Hashtable和HashMap確認key在數組中的索引的方法不同。
- Hashtable通過index = (hash & 0x7FFFFFFF) % tab.length;來確認
- HashMap通過i = (n - 1) & hash;來確認 get( Object key)
/** * 返回指定參數key映射的value,如果沒有對應映射,返回null * * @param key 指定參數 * @return 返回指定參數key映射的value,如果沒有對應映射,返回null * @throws NullPointerException 如果指定參數key為null * @see #put(Object, Object) */@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;}
常量MAX_ARRAY_SIZE
/** * 指派給arrays的最大容量 * 為什麼要減去8呢。 * 因為某些VM會在數組中保留一些頭字,嘗試分配這個最大儲存容量,可能會導致array容量大於VM的limit,最終導致OutOfMemoryError。 */private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
rehash()
/** * 增加hashtable的容量,為了更有效地存放和找到它的entry。 * 當索引值對的數量超過了臨界值(capacity*load factor)這個方法自動調用 * 長度變為原來的2倍+1 * */@SuppressWarnings("unchecked")protected void rehash() { //記錄舊容量 int oldCapacity = table.length; //記錄舊桶的數組 Entry<?,?>[] oldMap = table; // overflow-conscious code //新的容量為舊的容量的2倍+1 int newCapacity = (oldCapacity << 1) + 1; //如果新的容量大於容量的最大值MAX_ARRAY_SIZE if (newCapacity - MAX_ARRAY_SIZE > 0) { //如果舊容量為MAX_ARRAY_SIZE,容量不變,中斷方法的執行 if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; //如果舊容量不為MAX_ARRAY_SIZE,新容量變為MAX_ARRAY_SIZE newCapacity = MAX_ARRAY_SIZE; } //建立新的數組,容量為新容量 Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; //結構性修改次數+1 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; } }}
看完代碼,我們可以總結出rehash的總體思路為: 建立變數新的容量,值為舊的容量的2倍+1 如果新的容量大於容量的最大值MAX_ARRAY_SIZE
如果舊容量為MAX_ARRAY_SIZE,容量不變,中斷方法的執行 如果舊容量不為MAX_ARRAY_SIZE,新容量變為MAX_ARRAY_SIZE 建立新的數組,容量為新容量 將舊的數組中的索引值對轉移到新數組中
這裡可以看到,一般情況下,HashMap擴容後容量變為原來的兩倍,而Hashtable擴容後容量變為原來的兩倍+1。 addEntry( int hash, K key, V value, int index)
/** * 根據指參數向table中添加entry * put方法會使用此方法 */private void addEntry(int hash, K key, V value, int index) { //結構性修改次數+1 modCount++; //記錄現在的table Entry<?,?> tab[] = table; //如果現在的entry數量大於臨界值 if (count >= threshold) { // 擴容 rehash(); //記錄新的table tab = table; //重新計算key的hash hash = key.hashCode(); //重新計算index index = (hash & 0x7FFFFFFF) % tab.length; } // 建立一個新的entry @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; //將entry添加到table中 tab[index] = new Entry<>(hash, key, value, e); //table大小+1 count++;}
put(K key, V value)
/** * 添加指定索引值對到hashtable中 * 被添加的索引值對中的key和value都不能為null * * value可以通過get方法被取出。 * * @param key the hashtable key * @param value the value * @return 如果hashtable中已經存在key,則返回原來的value * @exception NullPointerException 如果key或者value為null * @see Object#equals(Object) * @see #get(Object) */public synchronized V put(K key, V value) { // 確認value不為null if (value == null) { throw new NullPointerException(); } Entry<?,?> tab[] = table; int hash = key.hashCode(); //找到key在table中的索引 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") //擷取key所在索引的entry Entry<K,V> entry = (Entry<K,V>)tab[index]; //遍曆entry,判斷key是否已經存在 for(; entry != null ; entry = entry.next) { //如果key已經存在 if ((entry.hash == hash) && entry.key.equals(key)) { //儲存舊的value V old = entry.value; //替換value entry.value = value; //返回舊的value return old; } } //如果key在hashtable不是已經存在,就直接將索引值對添加到table中,返回null addEntry(hash, key, value, index); return null;}
從代碼中可以總結出Hashtable的put方法的總體思路: 確認value不為null。如果為null,則拋出異常 找到key在table中的索引,擷取key所在位置的entry 遍曆entry,判斷key是否已經存在 如果key已經存在,替換value,返回舊的value 如果key在hashtable不是已經存在,就直接添加,否則直接將索引值對添加到table中,返回null
在方法中可以看到,在遍曆桶中元素時,是按照鏈表的方式遍曆的。可以印證,HashMap的桶中可能為鏈表或者樹。但Hashtable的桶中只可能是鏈表。 remove( Object key)
/** * 刪除hashtable中參數key映射的索引值對。如果參數key在hashtable不存在,方法不做任何操作。 * * @param key 參數key * @return 參數key映射的value,如果不存在對應的映射,返回null。 * @throws NullPointerException 如果key為null */public synchronized V remove(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); //計算key在hashtable中的索引 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; //遍曆entry,如果entry中存在key為參數key的索引值對,就刪除索引值對,並返回索引值對的value for(Entry<K,V> 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; } } //如果不存在key為參數key的索引值對,返回value return null;}
從代碼中可以總結出Hashtable的remove方法的總體思路: 找到key在table中的索引,擷取key所在位置的entry 遍曆entry,判斷key是否已經存在 如果key存在,刪除key映射的索引值對,返回舊的value 如果key在hashtable不存在,返回null putAll( Map<? extends K, ? extends V> t)
/** * 將指定t中所有的索引值對複製到hashtable中。 * 如果t中的索引值對的key在hashtable中已經存在,就替換。 * * @param t 指定參數t * @throws NullPointerException 如果指定參數t為null * @since 1.2 */public synchronized void putAll(Map<? extends K, ? extends V> t) { //遍曆參數t中所有的索引值對,將其複製到hashtable中 for (Map.Entry<? extends K, ? extends V> e : t.entrySet()) put(e.getKey(), e.getValue());}
clear()
/** * 清空hashtable中所有的索引值對 */public synchronized void clear() { Entry<?,?> tab[] = table; modCount++; //遍曆hashtable中所有的entry,將其置為null, for (int index = tab.length; --index >= 0; ) tab[index] = null; //修改hashtable大小為0 count = 0;}
clone()
/** * 建立一個hashtable的淺拷貝。 * hashtable所有的結構都被拷貝,但索引值對沒有拷貝。 * 這是個相對來說代價比較大的操作。 * * @return 一個hashtable的淺拷貝 */public synchronized Object clone() { try { //調用父類的clone方法,淺拷貝一個HashTable對象t Hashtable<?,?> t = (Hashtable<?,?>)super.clone(); //給table屬性賦值 t.table = new Entry<?,?>[table.length]; //遍曆原散列數組,單獨地拷貝並產生每個桶的鏈表。 for (int i = table.length ; i-- > 0 ; ) { t.table[i] = (table[i] != null) ? (Entry<?,?>) table[i].clone() : null; } //給keySet屬性賦值 t.keySet = null; //給entrySet屬性賦值 t.entrySet = null; //給values 屬性賦值 t.values = null; //給modCount 屬性賦值 t.modCount = 0; //返回淺拷貝 return t; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); }}
toString()
/** * 返回hashtable的字串表現形式。 * * @return hashtable的字串表現形式。 */public synchronized String toString() { int max = size() - 1; //如果hashtable大小為0,返回"{}" if (max == -1) return "{}"; //使用StringBuilder StringBuilder sb = new StringBuilder(); //擷取entry迭代器 Iterator<Map.Entry<K,V>> it = entrySet().iterator(); sb.append('{'); //遍曆hashtable for (int i = 0; ; i++) { Map.Entry<K,V> e = it.next(); K key = e.getKey(); V value = e.getValue(); //組裝hashtable的字串表示 sb.append(key == this ? "(this Map)" : key.toString()); sb.append('='); sb.append(value == this ? "(this Map)" : value.toString()); //i=max=size() - 1,到了最後一個entry if (i == max) return sb.append('}').toString(); sb.append(", "); }}
getEnumeration( int type)
/** * 擷取Hashtable的枚舉類對象 * * @param type 0——返回key的枚舉類對象/迭代器;1——返回values的枚舉類對象/迭代器,2——返回entry的枚舉類對象/迭代器 */private <T> Enumeration<T> getEnumeration(int type) { if (count == 0) { return Collections.emptyEnumeration(); } else { //false,意味著不是迭代器,如果為true,說明返回的是迭代器 return new Enumerator<>(type, false); }}// Enumerations/Iterations的類型。private static final int KEYS = 0;private static final int VALUES = 1;private static final int ENTRIES = 2;
getIterator( int type)
/** * 擷取Hashtable的迭代器 * * @param type 0——返回key的枚舉類對象/迭代器;1——返回values的枚舉類對象/迭代器,2——返回entry的枚舉類對象/迭代器 */private <T> Iterator<T> getIterator(int typ