本文版權歸 遠方的風lyh和博客園共有,歡迎轉載,但須保留此段聲明,並給出原文鏈接,謝謝合作,如有錯誤之處忘不吝批評指正!
HashTable內部存儲結構
HashTable內部存儲結構為數組+單向鏈表的形式存儲數據,即定義的 Entry<?,?>[] table 變量
源碼分析
變量定義
//使用Entry數組存儲數據 (Entry 單向鏈表) private transient Entry<?,?>[] table; //已經存儲在table 的 Entry 個數 private transient int count; /**** * Entry數組擴容閾值(count) count>=threshold 時擴容 Entry數組會進行擴容 * 建議不要設置超過1 更要不設置太大,導致鏈表長度過長 會導致查詢很慢 * 比如 HashTble initialCapacity =5 loadFactor=0.75 則計算 threshold =3 * when count >=3 時 table開始擴容(具體如何擴容的看擴容代碼) * ***/ private int threshold; //負載因子 用於計算 threshold private float loadFactor; //記錄修改次數 private transient int modCount = 0; //table數組的最大長度 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
Entry點向鏈表結構
/** * Hashtable bucket collision list entry *單向鏈表 */ private static class Entry<K,V> implements Map.Entry<K,V> { //hash值 final int hash; //key final K key; //value V value; //后繼 Entry<K,V> next; protected Entry(int hash, K key, V value, Entry<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } @SuppressWarnings("unchecked") protected Object clone() { return new Entry<>(hash, key, value, (next==null ? null : (Entry<K,V>) next.clone())); } // Map.Entry Ops public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { if (value == null) throw new NullPointerException(); V oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return (key==null ? e.getKey()==null : key.equals(e.getKey())) && (value==null ? e.getValue()==null : value.equals(e.getValue())); } public int hashCode() { return hash ^ Objects.hashCode(value); } public String toString() { return key.toString()+"="+value.toString(); } }
構造函數
/*** * 初始化HashTable 指定初始化容量(initialCapacity),負載因子(loadFactor) * HashTable初始化核心代碼 * **/ 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; //初始化Entry數組 table = new Entry<?,?>[initialCapacity]; //計算擴容閾值 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); } /*** * 初始化HashTable 指定初始化容量(initialCapacity) * 默認負載因子大小為0.75 * ***/ public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } /*** * 默認初始化 Entry數組 大小為 11 * loadFactor =0.75 ***/ public Hashtable() { this(11, 0.75f); } /*** * * ***/ public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }
put方法
/*** **添加元素 key value ** 注意:put方法是加鎖的 這就是 hashTable 為啥是線程安全的原因 阻塞的
** key、value 均不能為空 ****/ public synchronized V put(K key, V value) { // Make sure the value is not null value不能為空 if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; //hashCode取的是key的HashCode key不能為空 int hash = key.hashCode(); //根據hashCode & long最大值散列 再對 數組長度取模獲取到所要插入的數組下標 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") //查看數組該下標下是否存在元素 如存在便利 value(新)覆蓋key相同的值 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; } /*** *保存 ***/ private void addEntry(int hash, K key, V value, int index) { //記錄put次數 +1 modCount++; Entry<?,?> tab[] = table; //已存儲的Entry個數 >= 閾值 擴容 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鏈表 Entry<K,V> e = (Entry<K,V>) tab[index]; //將Entry<K,V> 保存 並將Entry<K,V>.next 指向原來的Entry鏈表 tab[index] = new Entry<>(hash, key, value, e); //數組長度+1 count++; }
擴容機制
/**** **擴容 ** 觸發 count(已存儲的Entry個數) >= threshold(閾值) ****/ protected void rehash() { //擴容前 數組(table)長度 int oldCapacity = table.length; //擴容前的 table數組 Entry<?,?>[] oldMap = table; // overflow-conscious code //擴容后的 數組長度為 oldCapacity*2+1 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; } // 新建一個數組長度為 oldCapacity*2+1 數組 Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; //計算閾值 threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; //將舊數組中的Entry 重新計算轉移到新數組中 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; } } }
特點:
-
- key、value 都不允許為空 key不允許為空 因為hashcode取的是key 該對象的HashCode()
- HashTable 默認構造方法 容量初始化為 11 負載因子為 0.75 若使用帶負載因子的構造方法創建HashTable 請不要講負載因子設置過大 比如 初始化容量設為 1 負載因子設為1000 這樣會導致查詢很慢
- HashTable是數組+單項鏈表Entry<k,v>的結構來存儲數據
- 數組table最大長度為 Integer.Max - 8;
- 擴容條件 count(HashTbale 存儲鏈表的個數) >= threshold(閾值 計算方法 table.length * loadFactor)
- 線程安全