一篇文章帶你搞定HashTable
在我們們開講原始碼之前,首先需要了解下什麼是雜湊表?
雜湊表(Hash table 又稱雜湊表),是根據關鍵碼值(Key value)而直接進行訪問的資料結構.也就是說,它通過把關鍵碼值對映到表中的一個位置來訪問記錄,以加快查詢的速度.這個對映函式就叫做雜湊函式,存放記錄的陣列叫做雜湊表. —— 百度百科
如圖:
在Java中HashTable以陣列
+連結串列
來實現,相對於HashMap來說要簡單得多.HashTable不同於HashMap,它內部不允許插入null
值,同時它是執行緒安全的,所有的讀寫操作都進行了鎖保護,但也難以避免的對讀寫效率產生了較大影響.因此在日常開發中為保證執行緒安全一般建議使用ConcurrentHashMap
.
為啥Java中
Hashtable
中的t
要小寫? 這不符合駝峰命名規則啊
大意: Hashtable
建立於Java1,而集合的統一命名規範是後來在Java2中建立的,而當時又釋出了新集合來代替它,再加上大量Java程式使用Hashtable
類,考慮到相容問題不可能將Hashtable
改為HashTable
.同時Hashtable
已經過時了,不建議在程式碼中使用.
原始碼分析
結構圖
繼承關係
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
Dictionary
是JDK1.0裡引入的抽象類,用於儲存鍵/值對,作用和Map類似.注意Dictionary
類已經過時了,在實際開發中,可以通過實現Map
介面來完成儲存鍵值對的功能.
類中屬性
/** 內部維護了一個 Entry 陣列 */
private transient Entry<?,?>[] table;
/** 雜湊表裡的元素數量 */
private transient int count;
/** 觸發擴容的閾值 */
private int threshold;
/** 載入因子 預設 0.75 */
private float loadFactor;
/** 記錄 涉及到結構變化的次數(offer/remove/clear等) */
private transient int modCount = 0;
/** 版本號 */
private static final long serialVersionUID = 1421746759512286392L;
table
陣列裡存的Entry
實際上是一個單向連結串列,雜湊表的鍵值對都是存在table
裡的.
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
...
}
建構函式
public Hashtable() { this(11, 0.75f); }
public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); }
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);
//初始容量最小為1
if (initialCapacity==0) initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
Hashtable的預設容量是11
,loadFactor
預設載入因子0.75
.threshold
為
陣列容量 * loadFactor
.
核心函式
新增函式
public synchronized V put(K key, V value) {
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
int hash = key.hashCode();
// &運算取正值,再取模計算位置
int index = (hash & 0x7FFFFFFF) % tab.length;
Entry<K,V> entry = (Entry<K,V>)tab[index];
// 如果這個hash和key都已經存在了,就把原來的value替換掉
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;
}
put
函式通過關鍵字synchronized
保證了執行緒安全.第一行就表明了Hashtable中value都不能為null
.通過hash & 0x7FFFFFFF
來規避掉負數,再進行分配位置.如果key經常存在了,則覆蓋舊值並返回舊值.核心通過addEntry
來新增元素.
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
// 若當前容量已經大於 閾值 進行rehash擴容
if (count >= threshold) { // threshold = count * loadFlor
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = (Entry<K,V>) tab[index];
// 最新插入的 排在最前面
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
addEntry
函式 先會判斷如果需要擴容 當前數量 >= 閾值
,呼叫rehash
進行擴容,否則連結串列疊加.
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// 新容量為老容量的一倍+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;
}
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;
}
}
}
rehash
函式先會把容量擴大1倍+1,然後建立一個newMap
,把oldMap
裡的元素遍歷複製到新的newMap
裡,這個過程是比較耗時的,同時此操作後陣列和連結串列裡元素的位置都會發生改變.
刪除函式
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
Entry<K,V> e = (Entry<K,V>)tab[index];
// 遍歷連結串列 e 當前節點, prev 上一個節點 next 下一個節點
for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
// 效能優化 先 進行hash 判斷
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;
}
remove
函式比較簡單,通過key
定位在陣列中的位置,再遍歷連結串列刪除元素.
獲取函式
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)) {
return (V)e.value;
}
}
return null;
}
get
函式和remove
函式比較類似,都是先通過key
定位在陣列中的位置,再迭代連結串列找到元素並直接返回.
迭代器
Hashtable
裡迭代器使用的是比較老的Enumeration
介面,其作用和Iterator
類似,只提供了遍歷的功能.雖然Enumeration
還未被廢棄,但現在程式碼裡已經很少使用了.本篇文章就不再講述了,有興趣的可以去翻翻程式碼~
public interface Enumeration<E> {
/** 判斷是否還有元素 */
boolean hasMoreElements();
/** 如果還有元素則返回下一個元素,否則拋NoSuchElementException異常 */
E nextElement();
}
結語
Hashtable
整體是比較簡單的,其內部充斥著大量的遍歷操作,當資料量大的時候操作會非常耗時,非特殊情況下是用不到的.
一般來說,在日常開發中非併發場景推薦使用HashMap
,併發場景下雖然可以用Hashtable
,但是更推薦使用ConcurrentHashMap
.
ConcurrentHashMap
內部雖然也是使用Synchronized
,但它是針對單個物件的鎖,相比於Hashtable
裡鎖的粒度更細,效率更高.
相關文章
- 一篇文章帶你搞定 SpringBoot 整合 Swagger2Spring BootSwagger
- MySQL命令,一篇文章替你全部搞定MySql
- 一篇文章搞定Markdown
- 一篇文章帶你搞定經典面試題之扔雞蛋問題面試題
- 一篇文章帶你吃透 Docker 原理Docker
- 一篇文章帶你認識 SpringSecuritySpringGse
- 一篇文章帶你入門Zookeeper
- 一篇文章帶你快速入門createjsJS
- 一篇文章搞定前端面試前端面試
- 一篇文章帶你搞定 SpringSecurity 配置多個HttpSecurity 和實現對於方法安全的控制SpringGseHTTP
- 一篇文章帶你瞭解——Kotlin協程Kotlin
- 一篇文章搞定Python中的類Python
- 一篇文章搞定Python多程式(全)Python
- 一篇文章搞定 MySQL 索引優化MySql索引優化
- 一篇文章搞定javascript氣泡排序JavaScript排序
- 一篇文章帶你瞭解介面自動化
- # 一篇文章帶你入門軟體測試
- 一篇文章帶你掌握效能測試工具——JmeterJMeter
- 一篇文章帶你弄懂Kerberos的設計思路ROS
- 一篇文章帶你讀懂Redis的哨兵模式Redis模式
- MySQL十種鎖,一篇文章帶你全解析MySql
- 一篇文章帶你瞭解HTML5 MathMLHTML
- 一篇文章帶你瞭解和使用Promise物件Promise物件
- 一篇文章帶你徹底搞懂join的用法
- 一篇文章帶你搞懂 etcd 3.5 的核心特性
- 一篇文章帶你初步瞭解—CSS特指度CSS
- 一篇文章搞定 javascript 正規表示式JavaScript
- 一篇文章搞定SpringMVC引數繫結SpringMVC
- 一篇文章帶你瞭解高可用架構分析架構
- 一篇文章帶你瞭解HTML格式化元素HTML
- 一篇文章帶你瞭解CSS 分頁例項CSS
- 一篇文章帶你入門SQL程式設計GIFUSQL程式設計
- 一篇文章帶你掌握Flex佈局的所有用法Flex
- 一篇文章帶你瞭解設計模式——建立者模式設計模式
- 【架構視角】一篇文章帶你徹底吃透Spring架構Spring
- 一篇文章帶你吃透hashmap(面試指南升級版)HashMap面試
- HashMap原始碼分析 —— 一篇文章搞定HashMap面試HashMap原始碼面試
- 一篇文章帶你瞭解如何測試訊息佇列佇列