Java8源碼-Hashtable(2)__hashtable

來源:互聯網
上載者:User

上一篇文章翻譯了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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.