1. 程式人生 > >Java基礎——HashTable原始碼分析

Java基礎——HashTable原始碼分析

HashTable是什麼

  1. HashTable是基於雜湊表的Map介面的同步實現
  2. HashTable中元素的key是唯一的,value值可重複
  3. HashTable中元素的key和value不允許為null,如果遇到null,則返回NullPointerException
  4. HashTable中的元素是無序的
public class Hashtable<K,V>  
    extends Dictionary<K,V>  
    implements Map<K,V>, Cloneable, java.io.Serializable{}

HashTable跟HashMap一樣,同樣是連結串列雜湊的資料結構,從原始碼中我們可以看出,Hashtable 繼承於Dictionary類,實現了Map, Cloneable,Serializable介面

  1. Dictionary類是任何可將鍵對映到相應值的類的抽象父類,每個鍵和值都是物件
  2. Dictionary原始碼註釋指出 Dictionary 這個類過時了,新的實現類應該實現Map介面

Hashtable成員變數

  1. table:一個Entry[]陣列型別,而Entry(在 HashMap 中有講解過)就是一個單向連結串列。雜湊表的”key-value鍵值對”都是儲存在Entry陣列中的
  2. count:Hashtable的大小,它是Hashtable儲存的鍵值對的數量
  3. threshold:Hashtable的閾值,用於判斷是否需要調整Hashtable的容量,threshold的值 = (容量 * 負載因子)
  4. loadFactor:負載因子
  5. modCount:用來實現fail-fast機制的

Hashtable構造方法

Hashtable 一共提供了 4 個構造方法

  1. public Hashtable(int initialCapacity, float loadFactor): 用指定初始容量和指定載入因子構造一個新的空雜湊表
  2. public Hashtable(int initialCapacity):用指定初始容量和預設的載入因子 (0.75) 構造一個新的空雜湊表
  3. public Hashtable():預設建構函式,容量為 11,載入因子為 0.75
  4. public Hashtable(Map< ? extends K, ? extends V> t):構造一個與給定的Map具有相同對映關係的新雜湊表

Hashtable的儲存

public synchronized V put(K key, V value) {
    //確保value不為null
    if (value == null) {
        throw new NullPointerException();
    }

    //確保key不在hashtable中
    //首先,通過hash方法計算key的雜湊值,並計算得出index值,確定其在table[]中的位置
    //其次,迭代index索引位置的連結串列,如果該位置處的連結串列存在相同的key,則替換value,返回舊的value
    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)) {
            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;
    }

    //將值插入,返回的為null
    Entry<K,V> e = tab[index];
    // 建立新的Entry節點,並將新的Entry插入Hashtable的index位置,並設定e為新的Entry的下一個元素
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
    return null;
}

儲存的流程如下:

  1. 判斷value是否為空,為空則丟擲異常
  2. 計算key的hash值,並根據hash值獲得key在table陣列中的位置index
    • 如果table[index]元素為空,將元素插入到table[index]位置
    • 如果table[index]元素不為空,則進行遍歷連結串列,如果遇到相同的key,則新的value替代舊的value,並返回舊 value,否則將元素插入到鏈頭,返回null

Hashtable的獲取

public synchronized V get(Object key) {
    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;
}

獲取的流程如下:

  1. 通過 hash()方法求得key的雜湊值
  2. 根據hash值得到index索引
  3. 迭代連結串列,返回匹配的key的對應的value,找不到則返回null

Hashtable遍歷方式

Hashtable有4種遍歷方式:

//1、使用keys()
Enumeration<String> en1 = table.keys();
    while(en1.hasMoreElements()) {
    en1.nextElement();
}

//2、使用elements()
Enumeration<String> en2 = table.elements();
    while(en2.hasMoreElements()) {
    en2.nextElement();
}

//3、使用keySet()
Iterator<String> it1 = table.keySet().iterator();
    while(it1.hasNext()) {
    it1.next();
}

//4、使用entrySet()
Iterator<Entry<String, String>> it2 = table.entrySet().iterator();
    while(it2.hasNext()) {
    it2.next();
}

Hashtable與HashMap的區別

Hashtable HashMap
方法是同步的 方法是非同步的
基於Dictionary類 基於AbstractMap,而AbstractMap基於Map介面的實現
key和value都不允許為null,遇到null,直接返回 NullPointerException key和value都允許為null,遇到key為null的時候,呼叫putForNullKey方法進行處理,而對value沒有處理
hash陣列預設大小是11,擴充方式是old*2+1 hash陣列的預設大小是16,而且一定是2的指數

多執行緒存在的問題

  1. 如果涉及到多執行緒同步時,建議採用HashTable
  2. 沒有涉及到多執行緒同步時,建議採用HashMap
  3. Collections 類中存在一個靜態方法:synchronizedMap(),該方法建立了一個執行緒安全的 Map 物件,並把它作為一個封裝的物件來返回

synchronizedMap()其實就是對Map的方法加層同步鎖,從原始碼中可以看出

//Collections.synchronizedMap(Map<K, V>)    
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
     return new SynchronizedMap<K,V>(m);
}


private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable {
    private static final long serialVersionUID = 1978198479659022715L;

    private final Map<K,V> m;     // Backing Map
    //同步鎖
    final Object      mutex;        // Object on which to synchronize

    SynchronizedMap(Map<K,V> m) {
        if (m==null)
            throw new NullPointerException();
        this.m = m;
        //把this本身作為鎖監視器, 這樣任何執行緒訪問他的方法都要獲取該監視器.
        mutex = this;
    }


    SynchronizedMap(Map<K,V> m, Object mutex) {
        this.m = m;
        this.mutex = mutex;
    }

    public int size() {
        synchronized(mutex) {return m.size();}
    }

    //重寫map的emty方法
    public boolean isEmpty() {
        synchronized(mutex) {return m.isEmpty();}
    }
    public boolean containsKey(Object key) {
        synchronized(mutex) {return m.containsKey(key);}
    }
    public boolean containsValue(Object value) {
        synchronized(mutex) {return m.containsValue(value);}
    }
    public V get(Object key) {
        synchronized(mutex) {return m.get(key);}
    }

    public V put(K key, V value) {
        synchronized(mutex) {return m.put(key, value);}
    }
    public V remove(Object key) {
        synchronized(mutex) {return m.remove(key);}
    }
    public void putAll(Map<? extends K, ? extends V> map) {
        synchronized(mutex) {m.putAll(map);}
    }
    public void clear() {
        synchronized(mutex) {m.clear();}
    }

    private transient Set<K> keySet = null;
    private transient Set<Map.Entry<K,V>> entrySet = null;
    private transient Collection<V> values = null;

    //重寫keySet方法
    public Set<K> keySet() {
        synchronized(mutex) {
            if (keySet==null)
                //mutex傳給SynchronizedSet, 這樣對於set內部操作也需要獲取鎖.
                keySet = new SynchronizedSet<K>(m.keySet(), mutex);
            return keySet;
        }
    }

    public Set<Map.Entry<K,V>> entrySet() {
        synchronized(mutex) {
            if (entrySet==null)
                entrySet = new SynchronizedSet<Map.Entry<K,V>>(m.entrySet(), mutex);
            return entrySet;
        }
    }

    public Collection<V> values() {
        synchronized(mutex) {
            if (values==null)
                values = new SynchronizedCollection<V>(m.values(), mutex);
            return values;
        }
    }

    public boolean equals(Object o) {
        synchronized(mutex) {return m.equals(o);}
    }
    public int hashCode() {
        synchronized(mutex) {return m.hashCode();}
    }
    public String toString() {
        synchronized(mutex) {return m.toString();}
    }
    private void writeObject(ObjectOutputStream s) throws IOException {
        synchronized(mutex) {s.defaultWriteObject();}
    }
}