探索Hashtable底層實現
阿新 • • 發佈:2020-12-21
前言
探索Hashtable
底層實現是基於JDK1.8
,它的資料結構是陣列 + 連結串列
。在不考慮執行緒是否安全的前提下,它的很多細節處理都不如HashMap,何況如今的HashMap又加了紅黑樹
,查詢修改肯定比不上,因為紅黑樹的時間複雜度是O(logN),而連結串列的時間複雜度是O(N),新增與刪除無法比較,畢竟兩者的策略不一致;而倘若比較討論併發的話,ConcurrentHashMap
比它更適合,Hashtable的作者也說了,看來是已經廢棄的節奏了。
資料結構
既然是與HashMap類似,那資料結構上肯定比它簡單,我們就速速通過!
//可克隆、序列化 public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { //雜湊表,負責儲存節點 private transient Entry<?,?>[] table; //雜湊表中節點的個數 private transient int count; /** * 閾值 = 初始容量 * 載入因子 當超過指定閾值時會對雜湊表重新雜湊,所有節點(連結串列)重新計算在新表中的索引,這就相當於所有節點都要參與計算、在新表中設定,效率非常地下 * HashMap是先將所有節點分成兩部分,最終只需要在新表中設定這兩部分即可 */ private int threshold; //載入因子,容量大小不變的情況下,載入因子過大減少空間開銷,增加查詢成本 private float loadFactor; //結構修改的次數 private transient int modCount = 0; //包含所有鍵的迭代器 private transient volatile Set<K> keySet; //包含所有鍵值對的迭代器 private transient volatile Set<Map.Entry<K,V>> entrySet; //包含所有值的迭代器 private transient volatile Collection<V> values; //最大容量,若設定過高的話可能會發生記憶體溢位 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //定義鍵型別,用於迭代器或列舉 private static final int KEYS = 0; //定義值型別,用於迭代器或列舉 private static final int VALUES = 1; //定義鍵值對型別,用於迭代器或列舉 private static final int ENTRIES = 2; }
建構函式
/** * 指定初始容量與載入因子構造雜湊表 * Float.isNaN:檢測是否是數字 * @param initialCapacity 指定初始容量 * @param loadFactor 指定載入因子 */ 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); //設定閾值 } /** * 指定初始容量與預設載入因子(0.75)構造雜湊表 * @param initialCapacity 指定初始容量 */ public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } /** * 預設初始容量(11)與預設載入因子(0.75)構造雜湊表 */ public Hashtable() { this(11, 0.75f); } /** * 將指定集合新增到雜湊表中,採用預設載入因子 * 設定儘可能大的初始容量以便減少重新雜湊的次數 * @param m 指定集合 */ public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }
簡單方法
/** * 獲取雜湊表中的元素個數 * @return 雜湊表中的元素個數 */ public synchronized int size() { return count; } /** * 判斷雜湊表是否為空 * @return 雜湊表是否為空 */ public synchronized boolean isEmpty() { return count == 0; } /** * 獲取包含所有鍵的列舉 * @return 包含所有鍵的列舉 */ public synchronized Enumeration<K> keys() { return this.<K>getEnumeration(KEYS); } /** * 獲取包含所有值的列舉 * @return 包含所有值的列舉 */ public synchronized Enumeration<V> elements() { return this.<V>getEnumeration(VALUES); } /** * 雜湊表中是否包含指定值 * @param value 指定值 * @return 是否包含指定值 */ public synchronized boolean contains(Object value) { if (value == null) { throw new NullPointerException(); } Entry<?,?> tab[] = 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; } /** * 雜湊表中是否包含指定值 * @param value 指定值 * @return 是否包含指定值 */ public boolean containsValue(Object value) { return contains(value); } /** * 雜湊表中是否包含指定鍵 * @param key 指定鍵 * @return 是否包含指定鍵 */ public synchronized boolean containsKey(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); /** * 0x7FFFFFFF = 0111 1111 1111 1111 1111 1111 1111 1111 * hash & 0x7FFFFFFF 是為了保證結果不出現負數的情況,否則負數取餘之後的結果也就為負數了,索引並沒有負數 * hash & 0x7FFFFFFF % tab.leng 是為了取在[0, tab.length -1]區間內的索引 */ 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; } /** * 指定鍵獲取值 * @param key 指定鍵 * @return 值 */ @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)) { //hash與equasl都相等才算是相等 return (V)e.value; } } return null; } /** * 擴容並重新雜湊所有節點 * 新容量 = 舊容量 * 2 + 1 */ protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; 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; } } } /** * 新增節點 * 採用頭插法,每新增一個節點就放到連結串列的頭部 * 節點個數超過閾值時會進行擴容 * @param hash 雜湊值 * @param key 指定鍵 * @param value 指定值 * */ private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; } /** * 新增節點 * 若發生重複則進行值替換 * @param key 指定鍵 * @param value 指定值 * @return null或舊值 */ public synchronized V put(K key, V value) { // Make sure the value is not null if (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]; for(; entry != null ; entry = entry.next) { //遍歷連結串列看看是否有重複的節點 if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; } /** * 指定鍵移除節點 * @param key 指定鍵 * @return null或舊值 */ public synchronized V remove(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) { //遍歷連結串列 prev:前一個節點 e:當前節點 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; } } return null; } /** * 批量新增節點 * @param m 指定集合 */ public synchronized void putAll(Map<? extends K, ? extends V> t) { for (Map.Entry<? extends K, ? extends V> e : t.entrySet()) put(e.getKey(), e.getValue()); } /** * 清空雜湊表 */ public synchronized void clear() { Entry<?,?> tab[] = table; modCount++; for (int index = tab.length; --index >= 0; ) tab[index] = null; count = 0; } /** * 淺拷貝 * @return 克隆後的物件 */ public synchronized Object clone() { try { Hashtable<?,?> t = (Hashtable<?,?>)super.clone(); t.table = new Entry<?,?>[table.length]; for (int i = table.length ; i-- > 0 ; ) { t.table[i] = (table[i] != null) ? (Entry<?,?>) table[i].clone() : null; } t.keySet = null; t.entrySet = null; t.values = null; t.modCount = 0; return t; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } /** * 根據型別獲取列舉 * @param type 型別 * @return 列舉 */ private <T> Enumeration<T> getEnumeration(int type) { if (count == 0) { return Collections.emptyEnumeration(); } else { return new Enumerator<>(type, false);//false不允許刪除 } } /** * 根據型別獲取迭代器 * @param type 型別 * @return 迭代器 */ private <T> Iterator<T> getIterator(int type) { if (count == 0) { return Collections.emptyIterator(); } else { return new Enumerator<>(type, true);//true允許刪除 } } /** * 獲取包含所有鍵的迭代器 * @return 包含所有鍵的迭代器 */ public Set<K> keySet() { if (keySet == null) keySet = Collections.synchronizedSet(new KeySet(), this);//執行緒安全 return keySet; } /** * 獲取包含所有鍵值對的迭代器 * @return 包含所有鍵值對的迭代器 */ public Set<Map.Entry<K,V>> entrySet() { if (entrySet==null) entrySet = Collections.synchronizedSet(new EntrySet(), this); return entrySet; } /** * 獲取包含所有值的迭代器 * @return 包含所有值的迭代器 */ public Collection<V> values() { if (values==null) values = Collections.synchronizedCollection(new ValueCollection(), this); return values; } //節點物件 private static class Entry<K,V> implements Map.Entry<K,V> { //當前節點的雜湊值 final int hash; //當前節點的鍵 final K key; //當前節點的值 V value; //當前節點的下一個節點 Entry<K,V> next; } //有些方法並沒有展示,請參考HashMap
總結
-
Hashtable的鍵值對不允許為空,因為它是直接拿這個鍵去獲取雜湊值,這不就造成空指標了(感覺有點白痴),它還對值做了空指標判斷。
-
Hashtable預設初始容量11、預設載入因子0.75。
-
Hashtable擴容時以
2倍 + 1
進行增長,舊錶中的所有節點重新雜湊到新表中,效率較為低下。 -
Hashtable計算索引時採用的是取餘,而HashMap採用的與運算。
-
Hashtable新增節點時採用的頭插法。
-
Hashtable計算hash值的方式可能會出現高位不同低位相同的兩個不同數造成最終的索引相同,相比hashMap,它的hash計算方式降低了碰撞的概率。
重點關注
鍵值對不允許null
計算索引採用取餘
頭插法