[学习] 哈希码:原理、用途与实现详解(C代码示例)

哈希码:原理、用途与实现详解

博主在《在C语言中使用字典》一文中,使用哈希来实现键值对的快速检索,今天对哈希这一算法工具,进行一些深入的研究,争取能能做到知其然亦知其所以然。

文章目录

  • 哈希码:原理、用途与实现详解
    • 一、哈希码的原理
      • [1.1 什么是哈希码?](#1.1 什么是哈希码?)
      • [1.2 哈希函数的特性](#1.2 哈希函数的特性)
      • [1.3 哈希冲突](#1.3 哈希冲突)
    • 二、哈希的表达式
      • [2.1 通用哈希函数表达式](#2.1 通用哈希函数表达式)
      • [2.2 多项式哈希函数(Polynomial Hashing)](#2.2 多项式哈希函数(Polynomial Hashing))
      • [2.3 DJB2 哈希函数](#2.3 DJB2 哈希函数)
      • [2.4 SHA-256 哈希函数](#2.4 SHA-256 哈希函数)
        • [(1) 初始化工作变量](#(1) 初始化工作变量)
        • [(2) 主压缩循环](#(2) 主压缩循环)
        • [(3) 更新哈希值](#(3) 更新哈希值)
      • [2.5 MD5 哈希函数](#2.5 MD5 哈希函数)
        • [(1) 初始化缓冲区](#(1) 初始化缓冲区)
        • [(2) 主循环(四组 16 轮)](#(2) 主循环(四组 16 轮))
        • [(3) 更新缓冲区](#(3) 更新缓冲区)
      • [2.6 哈希冲突解决方法的显性表达式](#2.6 哈希冲突解决方法的显性表达式)
        • [(1) 链地址法(拉链法)](#(1) 链地址法(拉链法))
        • [(2) 开放定址法(线性探测)](#(2) 开放定址法(线性探测))
        • [(3) 双散列函数法](#(3) 双散列函数法)
      • [5. 小结](#5. 小结)
    • 三、哈希码的用途
      • [3.1 数据检索](#3.1 数据检索)
      • [3.2 数据完整性验证](#3.2 数据完整性验证)
      • [3.3 密码存储](#3.3 密码存储)
      • [3.4 分布式系统](#3.4 分布式系统)
    • 四、哈希码的实现
    • 五、总结

哈希码(Hash Code)是计算机科学中的核心概念之一,广泛应用于数据存储、检索、安全等领域。本文将从原理数学表达式用途实现四个方面详细介绍哈希码,并提供一个完整的C语言实现示例。


一、哈希码的原理

1.1 什么是哈希码?

哈希码是通过哈希函数将任意长度的数据(如字符串、文件等)映射为固定长度数值的过程。其核心思想是通过数学变换,将复杂的数据结构转换为一个唯一的标识符。

1.2 哈希函数的特性

哈希函数具有以下关键特性:

  1. 单向性

    从原始数据计算哈希码是单向的,即无法通过哈希码反推出原始数据。
    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)

  2. 确定性

    相同的输入数据始终生成相同的哈希码。
    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)

  3. 敏感性

    输入的微小变化会导致哈希码的显著改变。
    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)

  4. 均匀性

    哈希码在输出空间中均匀分布,减少冲突概率。
    ∀ 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−1ai⋅xi)modm

其中:

  • s = a 0 a 1 ... a n − 1 s = a_0a_1\ldots a_{n-1} s=a0a1...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−1ci⋅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} h0h2h4h6=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} T1T2=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算法 的哈希码计算函数,该算法以低冲突率和高效性著称。

c 复制代码
#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;
}
代码解析:
  1. DJB2算法

    • 初始值 hash = 5381 是经验值,用于优化分布。
    • 每次迭代中,hash = ((hash << 5) + hash) + c 等价于 hash * 33 + c,其中 33 是经过实测的最优系数。
    • 最终返回的哈希码为无符号长整型。
  2. 编译与运行

    保存为 hash_code.c,使用以下命令编译并运行:

    bash 复制代码
    gcc -o hash_code hash_code.c
    ./hash_code
输出示例:
复制代码
Input: Hello, World!
Hash Code: 11557940046392859113

4.2 哈希表实现

以下是基于哈希码的简单哈希表实现,支持插入和查找操作。

c 复制代码
#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"));   // 输出: 10
    printf("banana: %d\n", find(&table, "banana")); // 输出: 20
    printf("grape: %d\n", find(&table, "grape"));   // 输出: -1

    free_table(&table);
    return 0;
}
代码解析:
  1. 哈希表结构

    • 使用数组 items 存储键值对指针。
    • TABLE_SIZE 定义哈希表大小。
  2. 插入与查找

    • 插入时通过哈希码取模定位数组索引。
    • 查找时直接通过哈希码定位索引,并比较键值。
  3. 冲突处理

    • 当多个键映射到同一索引时,当前实现会覆盖旧值。实际应用中可通过链地址法(链表)或开放寻址法解决冲突。

五、总结

哈希码是计算机科学中不可或缺的技术,其核心价值在于高效的数据映射与快速检索。通过合理设计哈希函数和冲突解决策略,可以显著提升系统的性能与安全性。本文通过理论讲解和代码实现,展示了哈希码的原理、用途及其实现方式。希望读者能通过本文深入理解哈希码的本质,并在实际开发中灵活应用。


研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)


相关推荐
懒惰的bit21 分钟前
STM32F103C8T6 学习笔记摘要(二)
笔记·stm32·学习
C羊驼1 小时前
C语言:排序算法
c语言·算法·排序算法
float_com1 小时前
【单调栈】-----【Largest Rectangle in a Histogram】
算法··单调栈
华科易迅2 小时前
人工智能学习57-TF训练
人工智能·学习·人工智能学习57-tf训练
虚!!!看代码2 小时前
【秒杀系统设计】
学习
↣life♚2 小时前
SAM2论文解读-既实现了视频的分割一切,又比图像的分割一切SAM更快更好
人工智能·深度学习·算法·计算机视觉·视频分割·通用分割
虾球xz2 小时前
CppCon 2017 学习:Howling at the Moon: Lua for C++ Programmers
开发语言·c++·学习·lua
鼓掌MVP2 小时前
“荔枝使”的难题怎么破:A*运输路径算法编程实践
算法
GIS小天2 小时前
AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年6月21日第115弹
人工智能·算法·机器学习·彩票
蓝胖子不会敲代码2 小时前
跟着AI学习C# Day29
人工智能·学习·c#