Java基礎——HashTable原始碼分析
阿新 • • 發佈:2019-01-03
HashTable是什麼
- HashTable是基於雜湊表的Map介面的同步實現
- HashTable中元素的key是唯一的,value值可重複
- HashTable中元素的key和value不允許為null,如果遇到null,則返回NullPointerException
- 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介面
- Dictionary類是任何可將鍵對映到相應值的類的抽象父類,每個鍵和值都是物件
- Dictionary原始碼註釋指出 Dictionary 這個類過時了,新的實現類應該實現Map介面
Hashtable成員變數
- table:一個Entry[]陣列型別,而Entry(在 HashMap 中有講解過)就是一個單向連結串列。雜湊表的”key-value鍵值對”都是儲存在Entry陣列中的
- count:Hashtable的大小,它是Hashtable儲存的鍵值對的數量
- threshold:Hashtable的閾值,用於判斷是否需要調整Hashtable的容量,threshold的值 = (容量 * 負載因子)
- loadFactor:負載因子
- modCount:用來實現fail-fast機制的
Hashtable構造方法
Hashtable 一共提供了 4 個構造方法
- public Hashtable(int initialCapacity, float loadFactor): 用指定初始容量和指定載入因子構造一個新的空雜湊表
- public Hashtable(int initialCapacity):用指定初始容量和預設的載入因子 (0.75) 構造一個新的空雜湊表
- public Hashtable():預設建構函式,容量為 11,載入因子為 0.75
- 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;
}
儲存的流程如下:
- 判斷value是否為空,為空則丟擲異常
- 計算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;
}
獲取的流程如下:
- 通過 hash()方法求得key的雜湊值
- 根據hash值得到index索引
- 迭代連結串列,返回匹配的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的指數 |
多執行緒存在的問題
- 如果涉及到多執行緒同步時,建議採用HashTable
- 沒有涉及到多執行緒同步時,建議採用HashMap
- 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();}
}
}