深入理解完美哈希

本文对完美 Hash 的概念进行了梳理,通过 Hash 构建步骤来了解它是如何解决 Hash 冲突的,并比较了 Hash 表和完美 Hash 表。下面介绍常见的 Hash 与 Perfect Hash 函数及它们在不同场景的应用。

散列函数(英语:Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或 hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。

Hash 函数是一种将集合 S 转换成具有固定长度的、不可逆的的集合 U 的单射,它的值一般为数字合字母的组合,Hash 函数拥有无限的输入空间,却只有有限的输出空间,这意味着 Hash 函数一定会产生碰撞,一个好的 Hash 函数可以显著的降低碰撞概率。Hash 函数一般有一下特征:

  1. 一致性。Hash 函数可以接受任意大小的数据,并输出固定长度的散列值,同时输出不同值的概率应该尽可能一致。如 CityHash128,不管原始数据有多大,计算得到的 hash 值总是 128 bit。

  2. 雪崩效应。原始数据哪怕只有一个字节的修改,得到的 hash 值都会发生巨大的变化。

  3. 单向。只能从原始数据计算得到 hash 值,不能从 hash 值计算得到原始数据。所以散列算法不是加密解密算法,加密解密是可逆的,散列算法是不可逆的。

  4. 避免冲突。几乎不可能找到一个数据和当前计算的这个数据计算出一样的 hash 值,因此散列函数能够确保数据的唯一性。在 Hash 函数保证不同值出现的概率一致的情况下,CityHash128 出现碰撞的概率只有 2 ^ -128。因为不同 Key 的碰撞概率很小,所以在某些情况下我们可以直接使用较短的 Hash 值代替较长原始数据存储。

Hash 函数

常见的 Hash 函数有:

  • CRC32:CRC32 能够快速的生成 32 位 Hash 值,一般在数据库系统或数据传输中出现,用于快速校验数据是否完整;

  • SipHash:SipHash 并不是为了速度设计的,与其他 Hash 函数相比速度上不占优势,而提供了 HashDoS 保护,是 Rust 中的 Hash 函数的默认实现,最新 Redis 中也在使用 SipHash;

  • MurMurHash:经典快速的 Hash 函数,目前最新的版本是 MurMurHash3,可以生成 32 位或者 128 位 Hash 值;

  • CityHash:来自于 Google 实现,受到 MurmurHash 启发,但是比 MurmurHash 更快,可以输出 64 位、128 位或者 256 位 Hash 值。ClickHouse 内置;

  • xxHash:针对小数据集速度非常快,支持输出 32 位、64 位、128 位 Hash 值,Github 开源,SSE 支持。ClickHouse 内置。

xxHash 的 benchmark,统计了常用 Hash 函数的性能:

常见用法:

Hash 表:通过 Hash 算法将 Key 均匀映射到不同的位置上,访问单个 key 时可以达到 O(1) 的平均时间复杂度,加快访问速度。

安全 Hash 函数

安全 Hash 函数(或者叫加密 Hash 函数)是一种优秀的 Hash 函数,无法(或者很难)通过 Hash 值猜测出 Key,更精确的说,安全 Hash 必须满足抗碰撞和不可逆两个条件:无法通过 Hash 值的统计学方法逆向,以及无法通过算法层逆向。常见的安全 Hash 算法包括:

  • SHA2,SHA3 系列

  • BLAKE 系列

SHA0、SHA1、MD5 算法已经被认为是不安全的,存在已知的漏洞,不要使用这些不安全的 Hash 函数来签名。

常见用法:

安全 Hash 函数广泛应用于数字签名技术中:对原文进行 Hash 后,将 Hash 结果通过私钥签名,避免原文被泄露或者被修改;工作量证明:如加密货币中挖矿就是通过给定值,计算符合条件的 Hash 输入;文件 ID:在网站下载地址旁往往提供了文件的 MD5 或者 SHA-1,确保下载的文件完整且没有被调包。

HashDoS 与全域 Hash(universal hash)

全域 Hash 解决的是确定性 Hash 算法无法应对特殊输入的问题。在链式 HashMap 里,假设 m = bucket size,考虑我们有输入集合 S 和 Hash 函数 H,其中 H = H’ % m,攻击者在知道 Hash 函数的情况下,容易构造集合 S 使得集合中每一个元素的 Hash 值相同,那 HashMap 会退化成链表。最坏情况下,HashMap 查找的时间复杂度变成了 O(n),插入 n 个元素时需要 O(n2) 的时间复杂度,所以也叫 HashDoS 攻击。

全域 Hash 解决的问题是:对于精心构造的输入,冲突率仍然在 1 / m。一个简单的想法是随机选一个 Hash 函数,不是在每一次操作时选一个,而是在输入前选一个 Hash 函数,之后所有的操作都基于该 Hash 函数。

当然 H 也不是随便定义的,具体来说是在 |H| 个 Hash 函数 H 中随机的选择一个 Hash 函数作为所有 key 的 Hash 函数,H 中所有的 Hash 函数 H’ 对于不相等的关键词 x 和 y,使得 H’(x) 和 H’(y) 值相等的函数 H’ 的数量个数等于 |H| / m,此时冲突概率为 1/m。

【文章福利】另外小编还整理了一些C++后台开发面试题,教学视频,后台学习路线图免费分享,需要的可以自行添加:Q群:720209036 点击加入~ 群文件共享

小编强力

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值