1. 程式人生 > 其它 >【C# 集合】HashTable .net core 中的Hashtable的實現原理

【C# 集合】HashTable .net core 中的Hashtable的實現原理

上一篇我介紹了Hash函式

這篇我來說一下Hash函式在 HashTable中的應用。

HashTable的特性:

1、裝載因子:.net core 0.72 ,java 0.75

2、衝突解決方案:Hashtable使用了閉雜湊法來解決衝突,java採用 開雜湊法解決衝突.

3、bucket(桶):用來轉載hash函式的返回值和建立key和hash返回值的關係

4、Hash函式以及演算法

5、HashTable是可以序列化的。是執行緒安全的。HashTable之所以是執行緒安全的,是因為方法上都加了synchronized關鍵字。

6、可列舉

.net core 中的Hashtable的實現原理

它通過一個結構體bucket來表示雜湊表中的單個元素,這個結構體中有三個成員:

private struct Bucket
        {
            public object? key;
            public object? val;
            public int hash_coll;   //  bucket結構體的hash_coll欄位所儲存的是h(key, i)的值而不是雜湊地址 
        }

(1) key :表示鍵,即雜湊表中的關鍵字。
(2) val :表示值,即跟關鍵字所對應值。
(3) hash_coll :它是一個int型別,用於表示鍵所對應的雜湊碼,.net core bucket結構體的hash_coll欄位所儲存的是h(key, i)的值而不是雜湊地址

int型別佔據32個位的儲存空間,它的最高位是符號位,為“0”時,表示這是一個正整數;為“1”時表示負整數。hash_coll使用最高位表示當前位置是否發生衝突,為“0”時,也就是為正數時,表示未發生衝突;為“1”時,表示當前位置存在衝突。之所以專門使用一個位用於存放雜湊碼並標註是否發生衝突,主要是為了提高雜湊表的執行效率。關於這一點,稍後會提到。

Hashtable解決衝突使用了雙重雜湊法,但又跟前面所講的雙重雜湊法稍有不同。它探測地址的方法如下:

h(key, i) = h1(key) + i * h2(key)

其中雜湊函式h1和h2的公式如下:

h1(key) = key.GetHashCode()

h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1))

由於使用了二度雜湊,最終的h(key, i)的值有可能會大於hashsize,所以需要對h(key, i)進行模運算,最終計算的雜湊地址為:

雜湊地址 = h(key, i) % hashsize

【注意】:bucket結構體的hash_coll欄位所儲存的是h(key, i)的值而不是雜湊地址。

雜湊表的所有元素存放於一個名稱為buckets(又稱為資料桶) 的bucket陣列之中,下面演示一個雜湊表的資料的插入和刪除過程,其中資料元素使用(鍵,值,雜湊碼)來表示。注意,本例假設Hashtable的長度為11,即hashsize = 11,這裡只顯示其中的前5個元素。

(1) 插入元素(k1,v1,1)和(k2,v2,2)。

由於插入的兩個元素不存在衝突,所以直接使用h1(key) % hashsize的值做為其雜湊碼而忽略了h2(key)。其效果如圖8.6所示。

(2) 插入元素(k3,v3,12)

新插入的元素的雜湊碼為12,由於雜湊表長為11,12 % 11 = 1,所以新元素應該插入到索引1處,但由於索引1處已經被k1佔據,所以需要使用h2(key)重新計算雜湊碼。

h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1))

h2(key) = 1 + ((12 >> 5) + 1) % (11 - 1)) = 2

新的雜湊地址為 h1(key) + i * h2(key) = 1 + 1 * 2 = 3,所以k3插入到索引3處。而由於索引1處存在衝突,所以需要置其最高位為“1”。

(10000000000000000000000000000001)2 = (-2147483647)10

最終效果如圖8.7所示。

(3) 插入元素(k4,v4,14)

k4的雜湊碼為14,14 % 11 = 3,而索引3處已被k3佔據,所以使用二度雜湊重新計算地址,得到新地址為14。索引3處存在衝突,所以需要置高位為“1”。

(12)10 = (00000000000000000000000000001100)2 高位置“1”後

(10000000000000000000000000001100)2 = (-2147483636)10

最終效果如圖8.8所示。

(4) 刪除元素k1和k2

Hashtable在刪除一個存在衝突的元素時(hash_coll為負數),會把這個元素的key指向陣列buckets,同時將該元素的hash_coll的低31位全部置“0”而保留最高位,由於原hash_coll為負數,所以最高位為“1”。

(10000000000000000000000000000000)2 = (-2147483648)10

單憑判斷hash_coll的值是否為-2147483648無法判斷某個索引處是否為空,因為當索引0處存在衝突時,它的hash_coll的值同樣也為-2147483648,這也是為什麼要把key指向buckets的原因。這裡把key指向buckets並且hash_coll值為-2147483648的空位稱為“有衝突空位”。如圖8.8所示,當k1被刪除後,索引1處的空位就是有衝突空位。

Hashtable在刪除一個不存在衝突的元素時(hash_coll為正數),會把鍵和值都設為null,hash_coll的值設為0。這種沒有衝突的空位稱為“無衝突空位”,如圖8.9所示,k2被刪除後索引2處就屬於無衝突空位,當一個Hashtable被初始化後,buckets陣列中的所有位置都是無衝突空位。

雜湊表通過關鍵字查詢元素時,首先計算出鍵的雜湊地址,然後通過這個雜湊地址直接訪問陣列的相應位置並對比兩個鍵值,如果相同,則查詢成功並返回;如果不同,則根據hash_coll的值來決定下一步操作。當hash_coll為0或正數時,表明沒有衝突,此時查詢失敗;如果hash_coll為負數時,表明存在衝突,此時需通過二度雜湊繼續計算雜湊地址進行查詢,如此反覆直到找到相應的鍵值表明查詢成功,如果在查詢過程中遇到hash_coll為正數或計算二度雜湊的次數等於雜湊表長度則查詢失敗。由此可知,將hash_coll的高位設為衝突位主要是為了提高查詢速度,避免無意義地多次計算二度雜湊的情況。

程式設計是個人愛好