【資料結構】HashTable原理及實現學習總結
有兩個類都提供了一個多種用途的hashTable機制,他們都可以將可以key和value結合起來構成鍵值對通過put(key,value)方法儲存起來,然後通過get(key)方法獲取相對應的value值。一個是前面提到的HashMap,還有一個就是馬上要講解的HashTable。對於HashTable而言,它在很大程度上和HashMap的實現差不多,如果我們對HashMap比較瞭解的話,對HashTable的認知會提高很大的幫助。他們兩者之間只存在幾點的不同,這個後面會闡述。
一、定義
HashTable在Java中的定義如下:
-
publicclass Hashtable<K,V>
- extends Dictionary<K,V>
- implements Map<K,V>, Cloneable, java.io.Serializable
從中可以看出HashTable繼承Dictionary類,實現Map介面。其中Dictionary類是任何可將鍵對映到相應值的類(如 Hashtable
)的抽象父類。每個鍵和每個值都是一個物件。在任何一個 Dictionary 物件中,每個鍵至多與一個值相關聯。Map是"key-value鍵值對"介面。
HashTable採用"拉鍊法"實現雜湊表,它定義了幾個重要的引數:table、count、threshold、loadFactor、modCount。
table:為一個Entry[]陣列型別,Entry代表了“拉鍊”的節點,每一個Entry代表了一個鍵值對,雜湊表的"key-value鍵值對"都是儲存在Entry陣列中的。
count:HashTable的大小,注意這個大小並不是HashTable的容器大小,而是他所包含Entry鍵值對的數量。
threshold:Hashtable的閾值,用於判斷是否需要調整Hashtable的容量。threshold的值="容量*載入因子"。
loadFactor:載入因子。
modCount:用來實現“fail-fast”機制的(也就是快速失敗)。所謂快速失敗就是在併發集合中,其進行迭代操作時,若有其他執行緒對其進行結構性的修改,這時迭代器會立馬感知到,並且立即丟擲ConcurrentModificationException異常,而不是等到迭代完成之後才告訴你(你已經出錯了)。
二、構造方法
在HashTabel中存在5個建構函式。通過這5個建構函式我們構建出一個我想要的HashTable。
[java] view plain copy print?- public Hashtable() {
- this(11, 0.75f);
- }
預設建構函式,容量為11,載入因子為0.75。
[java] view plain copy print?- public Hashtable(int initialCapacity) {
- this(initialCapacity, 0.75f);
- }
用指定初始容量和預設的載入因子 (0.75) 構造一個新的空雜湊表。
[java] view plain copy print?- public Hashtable(int initialCapacity, float loadFactor) {
- //驗證初始容量
- if (initialCapacity < 0)
- thrownew IllegalArgumentException("Illegal Capacity: "+
- initialCapacity);
- //驗證載入因子
- if (loadFactor <= 0 || Float.isNaN(loadFactor))
- thrownew IllegalArgumentException("Illegal Load: "+loadFactor);
- if (initialCapacity==0)
- initialCapacity = 1;
- this.loadFactor = loadFactor;
- //初始化table,獲得大小為initialCapacity的table陣列
- table = new Entry[initialCapacity];
- //計算閥值
- threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
- //初始化HashSeed值
- initHashSeedAsNeeded(initialCapacity);
- }
用指定初始容量和指定載入因子構造一個新的空雜湊表。其中initHashSeedAsNeeded方法用於初始化hashSeed引數,其中hashSeed用於計算key的hash值,它與key的hashCode進行按位異或運算。這個hashSeed是一個與例項相關的隨機值,主要用於解決hash衝突。
[java] view plain copy print?- privateint hash(Object k) {
- return hashSeed ^ k.hashCode();
- }
構造一個與給定的 Map 具有相同對映關係的新雜湊表。
[java] view plain copy print?- public Hashtable(Map<? extends K, ? extends V> t) {
- //設定table容器大小,其值==t.size * 2 + 1
- this(Math.max(2*t.size(), 11), 0.75f);
- putAll(t);
- }
三、主要方法
HashTable的API對外提供了許多方法,這些方法能夠很好幫助我們操作HashTable,但是這裡我只介紹兩個最根本的方法:put、get。
首先我們先看put方法:將指定 key
對映到此雜湊表中的指定 value
。注意這裡鍵key和值value都不可為空。
- publicsynchronized V put(K key, V value) {
- // 確保value不為null
- if (value == null) {
- thrownew NullPointerException();
- }
- /*
- * 確保key在table[]是不重複的
- * 處理過程:
- * 1、計算key的hash值,確認在table[]中的索引位置
- * 2、迭代index索引位置,如果該位置處的連結串列中存在一個一樣的key,則替換其value,返回舊值
- */
- Entry tab[] = table;
- int hash = hash(key); //計算key的hash值
- int index = (hash & 0x7FFFFFFF) % tab.length; //確認該key的索引位置
- //迭代,尋找該key,替換
- 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();
- tab = table;
- hash = hash(key);
- index = (hash & 0x7FFFFFFF) % tab.length;
- }
- // 在索引位置處插入一個新的節點
- Entry<K,V> e = tab[index];
- tab[index] = new Entry<>(hash, key, value, e);
- //容器中元素+1
- count++;
- returnnull;
- }
put方法的整個處理流程是:計算key的hash值,根據hash值獲得key在table陣列中的索引位置,然後迭代該key處的Entry連結串列(我們暫且理解為連結串列),若該連結串列中存在一個這個的key物件,那麼就直接替換其value值即可,否則在將改key-value節點插入該index索引位置處。如下:
首先我們假設一個容量為5的table,存在8、10、13、16、17、21。他們在table中位置如下:
然後我們插入一個數:put(16,22),key=16在table的索引位置為1,同時在1索引位置有兩個數,程式對該“連結串列”進行迭代,發現存在一個key=16,這時要做的工作就是用newValue=22替換oldValue16,並將oldValue=16返回。
在put(33,33),key=33所在的索引位置為3,並且在該連結串列中也沒有存在某個key=33的節點,所以就將該節點插入該連結串列的第一個位置。
在HashTabled的put方法中有兩個地方需要注意:
1、HashTable的擴容操作,在put方法中,如果需要向table[]中新增Entry元素,會首先進行容量校驗,如果容量已經達到了閥值,HashTable就會進行擴容處理rehash(),如下:
[java] view plain copy print?- protectedvoid rehash() {
- int oldCapacity = table.length;
- //元素
- Entry<K,V>[] oldMap = table;
- //新容量=舊容量 * 2 + 1
- int newCapacity = (oldCapacity << 1) + 1;
- if (newCapacity - MAX_ARRAY_SIZE > 0) {
- if (oldCapacity == MAX_ARRAY_SIZE)
- return;
- newCapacity = MAX_ARRAY_SIZE;
- }
- //新建一個size = newCapacity 的HashTable
- Entry<K,V>[] newMap = new Entry[];
- modCount++;
- //重新計算閥值
- threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY