哈希碼:原理、用途與實現詳解
博主在《在C語言中使用字典》一文中,使用哈希來實現鍵值對的快速檢索,今天對哈希這一算法工具,進行一些深入的研究,爭取能能做到知其然亦知其所以然。
文章目錄
- 哈希碼:原理、用途與實現詳解
- 一、哈希碼的原理
- 1.1 什么是哈希碼?
- 1.2 哈希函數的特性
- 1.3 哈希沖突
- 二、哈希的表達式
- 2.1 通用哈希函數表達式
- 2.2 多項式哈希函數(Polynomial Hashing)
- 2.3 DJB2 哈希函數
- 2.4 SHA-256 哈希函數
- (1) 初始化工作變量
- (2) 主壓縮循環
- (3) 更新哈希值
- 2.5 MD5 哈希函數
- (1) 初始化緩沖區
- (2) 主循環(四組 16 輪)
- (3) 更新緩沖區
- 2.6 哈希沖突解決方法的顯性表達式
- (1) 鏈地址法(拉鏈法)
- (2) 開放定址法(線性探測)
- (3) 雙散列函數法
- 5. 小結
- 三、哈希碼的用途
- 3.1 數據檢索
- 3.2 數據完整性驗證
- 3.3 密碼存儲
- 3.4 分布式系統
- 四、哈希碼的實現
- 4.1 C語言實現哈希碼計算
- 代碼解析:
- 輸出示例:
- 4.2 哈希表實現
- 代碼解析:
- 五、總結
哈希碼(Hash Code)是計算機科學中的核心概念之一,廣泛應用于數據存儲、檢索、安全等領域。本文將從原理、數學表達式、用途和實現四個方面詳細介紹哈希碼,并提供一個完整的C語言實現示例。
一、哈希碼的原理
1.1 什么是哈希碼?
哈希碼是通過哈希函數將任意長度的數據(如字符串、文件等)映射為固定長度數值的過程。其核心思想是通過數學變換,將復雜的數據結構轉換為一個唯一的標識符。
1.2 哈希函數的特性
哈希函數具有以下關鍵特性:
-
單向性
從原始數據計算哈希碼是單向的,即無法通過哈希碼反推出原始數據。
Hash ( x ) = h 但 x ≠ Hash ? 1 ( h ) \text{Hash}(x) = h \quad \text{但} \quad x \neq \text{Hash}^{-1}(h) Hash(x)=h但x=Hash?1(h) -
確定性
相同的輸入數據始終生成相同的哈希碼。
x 1 = x 2 ? Hash ( x 1 ) = Hash ( x 2 ) x_1 = x_2 \Rightarrow \text{Hash}(x_1) = \text{Hash}(x_2) x1?=x2??Hash(x1?)=Hash(x2?) -
敏感性
輸入的微小變化會導致哈希碼的顯著改變。
x 1 ~ x 2 但 Hash ( x 1 ) ≠ Hash ( x 2 ) x_1 \sim x_2 \quad \text{但} \quad \text{Hash}(x_1) \neq \text{Hash}(x_2) x1?~x2?但Hash(x1?)=Hash(x2?) -
均勻性
哈希碼在輸出空間中均勻分布,減少沖突概率。
? x , y , Hash ( x ) ≈ UniformDistribution \forall x, y, \quad \text{Hash}(x) \approx \text{UniformDistribution} ?x,y,Hash(x)≈UniformDistribution
1.3 哈希沖突
哈希沖突是指不同的輸入生成相同的哈希碼。由于哈希函數的輸出空間有限,而輸入空間無限,沖突是不可避免的。常見的解決方法包括:
- 鏈地址法:將沖突的鍵值對存儲在鏈表中。
- 開放尋址法:通過探查算法尋找空閑位置。
二、哈希的表達式
哈希函數的顯性表達式因具體算法而異,以下是幾種常見哈希函數的顯性表達式及其數學形式:
2.1 通用哈希函數表達式
哈希函數的基本形式為:
Addr = H ( key ) \text{Addr} = H(\text{key}) Addr=H(key)
其中:
- key \text{key} key:輸入數據(字符串、數值等)。
- H H H:哈希函數。
- Addr \text{Addr} Addr:輸出的哈希碼(通常為固定長度的整數或二進制串)。
2.2 多項式哈希函數(Polynomial Hashing)
多項式哈希函數通過將輸入字符串視為多項式系數,計算其模值:
H ( s ) = ( ∑ i = 0 n ? 1 a i ? x i ) m o d m H(s) = \left( \sum_{i=0}^{n-1} a_i \cdot x^i \right) \mod m H(s)=(i=0∑n?1?ai??xi)modm
其中:
- s = a 0 a 1 … a n ? 1 s = a_0a_1\ldots a_{n-1} s=a0?a1?…an?1?:輸入字符串,長度為 n n n。
- x x x:基數(常選質數,如 31、37)。
- m m m:模數(通常為哈希表大小)。
- m o d mod mod:取模(求余)運算。
示例:
對于字符串 “abc”(ASCII 碼分別為 97, 98, 99),若 x = 31 x=31 x=31, m = 1000 m=1000 m=1000:
H ( " a b c " ) = ( 97 ? 31 0 + 98 ? 31 1 + 99 ? 31 2 ) m o d 1000 H("abc") = (97 \cdot 31^0 + 98 \cdot 31^1 + 99 \cdot 31^2) \mod 1000 H("abc")=(97?310+98?311+99?312)mod1000
2.3 DJB2 哈希函數
DJB2 是一種簡單高效的哈希算法,其顯性表達式為:
hash = ( ( hash ? 5 ) + hash ) + c \text{hash} = ((\text{hash} \ll 5) + \text{hash}) + c hash=((hash?5)+hash)+c
其中:
- ? \ll ?:左移運算(等價于乘以 2 5 = 32 2^5 = 32 25=32)。
- c c c:當前字符的 ASCII 碼。
- 初始值 hash = 5381 \text{hash} = 5381 hash=5381。
等價數學形式:
hash new = ( hash old × 33 ) + c \text{hash}_{\text{new}} = (\text{hash}_{\text{old}} \times 33) + c hashnew?=(hashold?×33)+c
迭代公式:
hash = ∑ i = 0 n ? 1 c i ? 33 n ? 1 ? i \text{hash} = \sum_{i=0}^{n-1} c_i \cdot 33^{n-1-i} hash=i=0∑n?1?ci??33n?1?i
2.4 SHA-256 哈希函數
SHA-256 是一種密碼學哈希函數,其核心步驟包括多項式運算和位操作。以下是其關鍵步驟的顯性表達式:
(1) 初始化工作變量
a = h 0 , b = h 1 , c = h 2 , d = h 3 , e = h 4 , f = h 5 , g = h 6 , h = h 7 \begin{align*} a &= h_0, \quad b = h_1, \quad c = h_2, \quad d = h_3, \\ e &= h_4, \quad f = h_5, \quad g = h_6, \quad h = h_7 \end{align*} ae?=h0?,b=h1?,c=h2?,d=h3?,=h4?,f=h5?,g=h6?,h=h7??
其中 h 0 ~ h 7 h_0 \sim h_7 h0?~h7? 為固定初始值(十六進制):
h 0 = 0 x 6 a 09 e 667 , h 1 = 0 x b b 67 a e 85 , h 2 = 0 x 3 c 6 e f 372 , h 3 = 0 x a 54 f f 53 a , h 4 = 0 x 510 e 527 f , h 5 = 0 x 9 b 05688 c , h 6 = 0 x 1 f 83 d 9 a b , h 7 = 0 x 5 b e 0 c d 19 \begin{aligned} h_0 &= 0x6a09e667, \quad h_1 = 0xbb67ae85, \\ h_2 &= 0x3c6ef372, \quad h_3 = 0xa54ff53a, \\ h_4 &= 0x510e527f, \quad h_5 = 0x9b05688c, \\ h_6 &= 0x1f83d9ab, \quad h_7 = 0x5be0cd19 \end{aligned} h0?h2?h4?h6??=0x6a09e667,h1?=0xbb67ae85,=0x3c6ef372,h3?=0xa54ff53a,=0x510e527f,h5?=0x9b05688c,=0x1f83d9ab,h7?=0x5be0cd19?
(2) 主壓縮循環
對每個消息塊執行 64 輪迭代,每輪更新工作變量:
T 1 = h + Σ 1 ( e ) + Ch ( e , f , g ) + K t + W t T 2 = Σ 0 ( a ) + Maj ( a , b , c ) \begin{aligned} T_1 &= h + \Sigma_1(e) + \text{Ch}(e,f,g) + K_t + W_t \\ T_2 &= \Sigma_0(a) + \text{Maj}(a,b,c) \end{aligned} T1?T2??=h+Σ1?(e)+Ch(e,f,g)+Kt?+Wt?=Σ0?(a)+Maj(a,b,c)?
其中:
- Σ 0 ( x ) = ROTR 2 ( x ) ⊕ ROTR 13 ( x ) ⊕ ROTR 22 ( x ) \Sigma_0(x) = \text{ROTR}^2(x) \oplus \text{ROTR}^{13}(x) \oplus \text{ROTR}^{22}(x) Σ0?(x)=ROTR2(x)⊕ROTR13(x)⊕ROTR22(x)
- Σ 1 ( x ) = ROTR 6 ( x ) ⊕ ROTR 11 ( x ) ⊕ ROTR 25 ( x ) \Sigma_1(x) = \text{ROTR}^6(x) \oplus \text{ROTR}^{11}(x) \oplus \text{ROTR}^{25}(x) Σ1?(x)=ROTR6(x)⊕ROTR11(x)⊕ROTR25(x)
- Ch ( x , y , z ) = ( x ∧ y ) ⊕ ( ? x ∧ z ) \text{Ch}(x,y,z) = (x \land y) \oplus (\lnot x \land z) Ch(x,y,z)=(x∧y)⊕(?x∧z)
- Maj ( x , y , z ) = ( x ∧ y ) ⊕ ( x ∧ z ) ⊕ ( y ∧ z ) \text{Maj}(x,y,z) = (x \land y) \oplus (x \land z) \oplus (y \land z) Maj(x,y,z)=(x∧y)⊕(x∧z)⊕(y∧z)
- K t K_t Kt?:第 t t t 輪的常量。
- W t W_t Wt?:消息擴展后的第 t t t 個字。
(3) 更新哈希值
每輪迭代后更新工作變量:
h = g , g = f , f = e , e = d + T 1 d = c , c = b , b = a , a = T 1 + T 2 \begin{aligned} h &= g, \quad g = f, \quad f = e, \quad e = d + T_1 \\ d &= c, \quad c = b, \quad b = a, \quad a = T_1 + T_2 \end{aligned} hd?=g,g=f,f=e,e=d+T1?=c,c=b,b=a,a=T1?+T2??
2.5 MD5 哈希函數
MD5 將輸入分塊處理,每輪進行四組非線性變換。以下是其核心步驟的顯性表達式:
(1) 初始化緩沖區
A = 0 x 67452301 , B = 0 x E F C D A B 89 , C = 0 x 98 B A D C F E , D = 0 x 10325476 A = 0x67452301, \quad B = 0xEFCDAB89, \quad C = 0x98BADCFE, \quad D = 0x10325476 A=0x67452301,B=0xEFCDAB89,C=0x98BADCFE,D=0x10325476
(2) 主循環(四組 16 輪)
每組使用不同的非線性函數 F , G , H , I F, G, H, I F,G,H,I:
- 第一組($ F(X,Y,Z) = (X \land Y) \lor (\lnot X \land Z) $):
T = D + ? s ( A + F ( B , C , D ) + X k + T i ) , 其中? s ∈ { 7 , 12 , 17 , 22 } T = D + \lll^s (A + F(B,C,D) + X_k + T_i), \quad \text{其中 } s \in \{7,12,17,22\} T=D+?s(A+F(B,C,D)+Xk?+Ti?),其中?s∈{7,12,17,22} - 第二組($ G(X,Y,Z) = (X \land Z) \lor (Y \land \lnot Z) $):
T = C + ? s ( D + G ( A , B , C ) + X k + T i ) , s ∈ { 5 , 9 , 14 , 20 } T = C + \lll^s (D + G(A,B,C) + X_k + T_i), \quad s \in \{5,9,14,20\} T=C+?s(D+G(A,B,C)+Xk?+Ti?),s∈{5,9,14,20} - 第三組($ H(X,Y,Z) = X \oplus Y \oplus Z $):
T = B + ? s ( C + H ( A , B , C ) + X k + T i ) , s ∈ { 4 , 11 , 16 , 23 } T = B + \lll^s (C + H(A,B,C) + X_k + T_i), \quad s \in \{4,11,16,23\} T=B+?s(C+H(A,B,C)+Xk?+Ti?),s∈{4,11,16,23} - 第四組($ I(X,Y,Z) = Y \oplus (X \lor \lnot Z) $):
T = A + ? s ( B + I ( A , B , C ) + X k + T i ) , s ∈ { 6 , 10 , 15 , 21 } T = A + \lll^s (B + I(A,B,C) + X_k + T_i), \quad s \in \{6,10,15,21\} T=A+?s(B+I(A,B,C)+Xk?+Ti?),s∈{6,10,15,21}
(3) 更新緩沖區
每組循環后更新 A , B , C , D A, B, C, D A,B,C,D:
A = A + T , B = B + new?value , C = C + new?value , D = D + new?value \begin{aligned} A &= A + T, \quad B = B + \text{new value}, \\ C &= C + \text{new value}, \quad D = D + \text{new value} \end{aligned} AC?=A+T,B=B+new?value,=C+new?value,D=D+new?value?
2.6 哈希沖突解決方法的顯性表達式
(1) 鏈地址法(拉鏈法)
對于哈希表 T T T,沖突鍵值對存儲在鏈表中:
T [ index ] = LinkedList ( key 1 , key 2 , … ) T[\text{index}] = \text{LinkedList}(\text{key}_1, \text{key}_2, \ldots) T[index]=LinkedList(key1?,key2?,…)
其中 index = H ( key ) m o d TABLE_SIZE \text{index} = H(\text{key}) \mod \text{TABLE\_SIZE} index=H(key)modTABLE_SIZE。
(2) 開放定址法(線性探測)
沖突時按固定步長 p ( i ) = i p(i) = i p(i)=i 探查:
index i = ( H ( key ) + i ) m o d TABLE_SIZE \text{index}_i = (H(\text{key}) + i) \mod \text{TABLE\_SIZE} indexi?=(H(key)+i)modTABLE_SIZE
直到找到空閑位置。
(3) 雙散列函數法
沖突時使用第二個哈希函數 H ′ H' H′:
index i = ( H ( key ) + i ? H ′ ( key ) ) m o d TABLE_SIZE \text{index}_i = (H(\text{key}) + i \cdot H'(\text{key})) \mod \text{TABLE\_SIZE} indexi?=(H(key)+i?H′(key))modTABLE_SIZE
5. 小結
不同哈希函數的顯性表達式反映了其設計思想和數學特性。例如:
- 多項式哈希通過多項式展開實現均勻分布。
- SHA-256/MD5依賴復雜的位運算和非線性函數保證安全性。
- 沖突解決方法通過數學公式動態調整存儲位置。
在實際應用中,選擇哈希函數時需權衡 速度、均勻性、抗碰撞性 等因素。
三、哈希碼的用途
3.1 數據檢索
哈希碼通過快速定位數據位置,顯著提升檢索效率。例如:
- 哈希表:通過哈希函數將鍵映射到數組索引,實現 O ( 1 ) O(1) O(1) 時間復雜度的查找。
- 數據庫索引:B+樹結合哈希加速數據查詢。
3.2 數據完整性驗證
哈希碼可用于驗證數據是否被篡改。例如:
- 文件校驗:通過比較文件的哈希碼與已知值判斷文件是否被修改。
- 數字簽名:對文檔生成哈希碼后加密,接收方解密后驗證哈希碼一致性。
3.3 密碼存儲
哈希碼在密碼學中用于安全存儲密碼。例如:
- 密碼哈希同步:將用戶密碼的哈希值從本地系統同步到云端。
- 加鹽哈希:在密碼中添加隨機值(鹽),防止彩虹表攻擊。
3.4 分布式系統
哈希碼在分布式系統中用于數據分片和負載均衡。例如:
- 一致性哈希:動態分配節點時減少數據遷移。
- 布隆過濾器:通過多個哈希函數檢測元素是否存在。
四、哈希碼的實現
4.1 C語言實現哈希碼計算
以下是一個基于 DJB2算法 的哈希碼計算函數,該算法以低沖突率和高效性著稱。
#include <stdio.h>
#include <string.h>// 哈希碼計算函數(DJB2算法)
unsigned long hash_code(const char *str) {unsigned long hash = 5381;int c;while ((c = *str++)) {hash = ((hash << 5) + hash) + c; // hash * 33 + c}return hash;
}int main() {const char *test_str = "Hello, World!";unsigned long code = hash_code(test_str);printf("Input: %s\n", test_str);printf("Hash Code: %lu\n", code);return 0;
}
代碼解析:
-
DJB2算法
- 初始值
hash = 5381
是經驗值,用于優化分布。 - 每次迭代中,
hash = ((hash << 5) + hash) + c
等價于hash * 33 + c
,其中33
是經過實測的最優系數。 - 最終返回的哈希碼為無符號長整型。
- 初始值
-
編譯與運行
保存為hash_code.c
,使用以下命令編譯并運行:gcc -o hash_code hash_code.c ./hash_code
輸出示例:
Input: Hello, World!
Hash Code: 11557940046392859113
4.2 哈希表實現
以下是基于哈希碼的簡單哈希表實現,支持插入和查找操作。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define TABLE_SIZE 100// 鍵值對結構體
typedef struct {char *key;int value;
} KeyValuePair;// 哈希表結構體
typedef struct {KeyValuePair *items[TABLE_SIZE];int size;
} HashTable;// 哈希碼計算函數
unsigned long hash_code(const char *str) {unsigned long hash = 5381;int c;while ((c = *str++)) {hash = ((hash << 5) + hash) + c;}return hash;
}// 插入鍵值對
void insert(HashTable *table, const char *key, int value) {unsigned long index = hash_code(key) % TABLE_SIZE;KeyValuePair *pair = (KeyValuePair *)malloc(sizeof(KeyValuePair));pair->key = strdup(key);pair->value = value;table->items[index] = pair;table->size++;
}// 查找鍵值對
int find(HashTable *table, const char *key) {unsigned long index = hash_code(key) % TABLE_SIZE;if (table->items[index] && strcmp(table->items[index]->key, key) == 0) {return table->items[index]->value;}return -1; // 未找到
}// 釋放哈希表
void free_table(HashTable *table) {for (int i = 0; i < TABLE_SIZE; i++) {if (table->items[i]) {free(table->items[i]->key);free(table->items[i]);}}free(table);
}int main() {HashTable table = {0};insert(&table, "apple", 10);insert(&table, "banana", 20);insert(&table, "orange", 30);printf("apple: %d\n", find(&table, "apple")); // 輸出: 10printf("banana: %d\n", find(&table, "banana")); // 輸出: 20printf("grape: %d\n", find(&table, "grape")); // 輸出: -1free_table(&table);return 0;
}
代碼解析:
-
哈希表結構
- 使用數組
items
存儲鍵值對指針。 TABLE_SIZE
定義哈希表大小。
- 使用數組
-
插入與查找
- 插入時通過哈希碼取模定位數組索引。
- 查找時直接通過哈希碼定位索引,并比較鍵值。
-
沖突處理
- 當多個鍵映射到同一索引時,當前實現會覆蓋舊值。實際應用中可通過鏈地址法(鏈表)或開放尋址法解決沖突。
五、總結
哈希碼是計算機科學中不可或缺的技術,其核心價值在于高效的數據映射與快速檢索。通過合理設計哈希函數和沖突解決策略,可以顯著提升系統的性能與安全性。本文通過理論講解和代碼實現,展示了哈希碼的原理、用途及其實現方式。希望讀者能通過本文深入理解哈希碼的本質,并在實際開發中靈活應用。
研究學習不易,點贊易。
工作生活不易,收藏易,點收藏不迷茫 :)