HashTable 源码解读
很多人都知道HashTable與HashMap的關系,HashTable是線程安全的,HashMap是非線程安全的。在介紹完HashMap之后,趁熱介紹一下HashTable。在HashTable中沒有像HashMap中那么多關于數據結構的內容。HashTable是線程安全的,因為其源碼的方法里都帶有synchronized,但是效率不高,如果想使用高性能的Hash結構,建議使用java.util.concurrent.ConcurrentHashMap
HashTable 存儲的數據類型
HashTable的key和value都可以為空,在存儲的過程中 key必須實現 hashCode()和equals()兩個方法。
影響HashTable性能的兩個參數
HashTable中的兩個變量影響其性能:初始容量與負載系數(load factor)。
容量
指的時hashtable中桶的個數。桶其實就是單向的鏈表。hashtable 是允許hash 沖突的,單個桶(鏈表)可以存儲多個entry。在定義HashTable的初始容量的大小時,要權衡是空間 和 重新hash運算(很耗時)之間的利弊。當初始的容量大于元素的最大個數時,將不會發生rehash運算,但是太大的初始容量意味著浪費了很多空間。如果能提前估算出要向hashTable中存很多值時,就要給一個適合的初始容量,因為在添加數據時如,果不需要rehash操作的話將會更快。
負載系數
指的是hashtable在自動擴容之前允許桶多滿?默認的負載系數為0.75,增大可以減少每次擴容的大小,但是增加了查找所花費的時間。
數據結構
前面也提到了,HashTable內部存儲了一個table數組,這個數組的每一個元素存儲的都是鏈表的頭。在存儲數值時,定位存儲位置是通過如下代碼:
int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length; // 去掉符號位的影響Entry<K,V> e = (Entry<K,V>)tab[index];上面的代碼就確定了當前key的節點位于哪個鏈表上,e 即鏈表頭。如果在該鏈表中無法找到對應的key,則將當前的節點添加到鏈表的頭部。
Entry<K,V> e = (Entry<K,V>) tab[index];// 把鏈表的頭部傳進去,為了將new 出來的節點.next指向原來鏈表的頭部tab[index] = new Entry<>(hash, key, value, e);rehash算法
rehash算法,也可以理解為擴容算法,當table裝不下要存儲的值的時候,這是后就需要擴容增加內部數組的長度,這下慘了,每個key存儲到哪個鏈表中是和table.length有直接關系的,所以在擴容時,要把當前hashtable中存儲的節點重新計算一遍存儲位置,這就是前面提到的為什么rehash會很耗時。
protected void rehash() {int oldCapacity = table.length;Entry<?,?>[] oldMap = table;// overflow-conscious codeint newCapacity = (oldCapacity << 1) + 1; // 容量每次擴大一倍if (newCapacity - MAX_ARRAY_SIZE > 0) {if (oldCapacity == MAX_ARRAY_SIZE)// Keep running with MAX_ARRAY_SIZE bucketsreturn;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; // 計算當前節點在newMap中存儲的位置// 每次插入數據都插入到鏈表的頭e.next = (Entry<K,V>)newMap[index]; // 將當前節點的指針指向原來鏈表的頭newMap[index] = e; // 將當且節點存入數組中}} }compute方法
這不是hashMap獨有的,是Map接口定義的。放在這里講的原因是:HashTable沒什么好寫的,正好從HashMap把這部分內容搬過來。
computeIfAbsent,computeIfPresent,compute 三個方法,這三個方法本質上都是根據給定的key更新當前map中的值,HashMap中也有同樣的方法
下面是一個簡短的例子
下面對3個方法進行一下介紹
computeIfAbsent
根據給定的key 在hashtable中查找,如果找到了返回key對應的值,如果沒找到,根據定義的計算功能,算出新值,如果新值不為空,添加到hashtable中 public synchronized V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {//計算功能不能為空Objects.requireNonNull(mappingFunction);// 緩存內部tableEntry<?,?> tab[] = table;// 根據給定的key 計算出hashint hash = key.hashCode();// 根據hash求出在數組第幾個鏈上int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>)tab[index];// 如果在鏈表中找到,則返回舊值for (; e != null; e = e.next) {if (e.hash == hash && e.key.equals(key)) {// Hashtable not accept null valuereturn e.value;}}// 記錄modCount 在計算時,不允許修改hashtable結構int mc = modCount;// 獲得根據計算功能計算出的新值V newValue = mappingFunction.apply(key);if (mc != modCount) { throw new ConcurrentModificationException(); }// 如果新值不為空,添加到hashtable中if (newValue != null) {addEntry(hash, key, newValue, index);}// 返回新值return newValue; }computeIfPresent
根據給定的key在hashtable中查找,如果沒找到,返回空,如果找到了,根據定義的功能,計算出新值,如果新值為 null,則將key對應的節點刪除,如果不是空,更新節點值,最后返回新值。 public synchronized V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {//定義的計算功能不能為空Objects.requireNonNull(remappingFunction);//復制一份tableEntry<?,?> tab[] = table;// 根據hash計算在hashtable中的位置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) {if (e.hash == hash && e.key.equals(key)) {// 如果找到了,根據key、舊值和定義的功能計算出新值 int mc = modCount;V newValue = remappingFunction.apply(key, e.value);if (mc != modCount) {throw new ConcurrentModificationException();}// 要將新值賦值到原來的key上,如果新值為空,則要在鏈表上刪除對應的節點,計數器-1if (newValue == null) {if (prev != null) {prev.next = e.next; } else {tab[index] = e.next;}modCount = mc + 1;count--;} else {e.value = newValue;}// 返回新值return newValue;}}// 如果在對應位置上的鏈表中沒有找到,則返回空return null; }compute
思路就是有就更新,沒有就添加,是上面兩個的整合。 public synchronized V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {// 定義的計算功能不能為空Objects.requireNonNull(remappingFunction);Entry<?,?> tab[] = table;// 根據hash獲取鏈表位置int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>)tab[index];如果根據key找到了對應的節點,更新對應的節點值,并返回根據功能計算的新值for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {if (e.hash == hash && Objects.equals(e.key, key)) {int mc = modCount;V newValue = remappingFunction.apply(key, e.value);if (mc != modCount) {throw new ConcurrentModificationException();}if (newValue == null) {if (prev != null) {prev.next = e.next;} else {tab[index] = e.next;}modCount = mc + 1;count--;} else {e.value = newValue;}return newValue;}}// 如果沒有找到根據key 計算出新值,如果新值不為空添加到table中,返回計算的新值int mc = modCount;V newValue = remappingFunction.apply(key, null);if (mc != modCount) { throw new ConcurrentModificationException(); }if (newValue != null) {addEntry(hash, key, newValue, index);}return newValue; }轉載于:https://www.cnblogs.com/arax/p/8206702.html
總結
以上是生活随笔為你收集整理的HashTable 源码解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到自己大肚子怀孕了什么预兆
- 下一篇: 【easy】234. Palindrom