Fork me on GitHub

哈希算法

常识点:英文“hash”,中文有翻译为哈希的,也有翻译成散列的。

哈希算法:将任意长度的二进制值串映射为固定长度二进制值串,这个映射规则就是哈希算法,而通过原始数据映射之后得到的二进制值串就是哈希值。

优秀的哈希算法应该满足的几点:
  • 从哈希值不能反向推导出原始数据(所以哈希算法也叫单向哈希算法
  • 对输入数据非常敏感,哪怕原始数据只修改了一个Bit,最终得到的哈希值也大不相同
  • 散列冲突的概率要很小,对于不同的原始数据,哈希值相同的概率要很小。
  • 哈希算法的执行效率要尽量高效,针对较长的文本,也能快速地计算出哈希值

哈希算法的应用

应用一:安全加密

我们常见的用于加密的哈希算法有

  • MD5(Message-Digest Algorithm 消息摘要算法)
  • SHA(Secure Hash Algorithm 安全散列算法)
  • DES (Data Encryption Standard 数据加密标准)
  • AES (Advanced Encryption Standard 高级加密标准)

对于加密算法,以上四点有两点格外重要

  1. 很难根据哈希值反向推导出原始数据(加密的目的就是要放置原始数据泄漏
  2. 哈希冲突的概率要很小(理论上冲突不可能避免,只能减小碰撞冲突的概率)

知识扩展:鸽巢原理

为什么哈希算法无法做到零冲突?

哈希算法产生的哈希值是有固定长度的,如之前的MD5哈希值,是固定的128位二进制串,能表示的数据是有限的,最多能表示 2^128个数据。而如果我们要对2^128+1个数据求哈希值,则必然存在哈希相同的情况。一般情况下,哈希值越长的哈希算法,哈希冲突的概率也就越低。

越复杂,越难破解的哈希算法,需要的计算时间也越长。

应用二:唯一标识

例子:海量图库搜寻一张图是否存在。
将图片的二进制码选取头尾字节,MD5得到哈希值,作为图片的唯一标识。放入散列表(键是哈希值,值是图片的路径),这样搜索的时间复杂度是O(1),找到图片后再对比二进制信息是否一致。

应用三:数据校验

下载文件,如何知道文件是否被篡改过?种子文件里有文件的哈希值,下载完成后将文件用相同的哈希函数生成哈希值,如果与种子文件里的哈希值一致就说明未被篡改。

应用四:散列函数

散列函数是设计散列表的一个关键,它直接决定了散列冲突的概率和散列表的性能。不过,相对于哈希函数的其他应用,散列函数对散列算法冲突的要求低很多。即便出现个别散列冲突,也可以通过开放寻址法或开链法解决。

不仅如此,散列函数对于散列计算得到的散列值,能够被反向解密也不关系。这里更加关注散列得到的值能否均匀分布,除此之外,散列算法的计算快慢,也影响散列表的性能。所以,散列函数用的散列算法一遍都比较简单,比较追求效率。

应用五:负载均衡

负载均衡的算法:加权、随机、加权轮询等。如果要实现一个会话粘滞的负载均衡算法,那么最简单的方案就是维护一个映射表(客户端id或会话id与服务器编号的映射关系),但这种方案有些弊端:

  • 如果客户端很多,那么映射表会很大,比较浪费内存空间
  • 客户端上线、下线、服务器扩容、缩容都会导致映射失效,这样维护这个映射表的成本就非常大

借助哈希算法,对客户端IP或会话ID进行哈希计算,将取得的哈希值与服务器列表大小进行取模运算,最终得到的值就是应该被路由到的服务器编号。这种方式来保证同一个IP过来的请求都路由到同一台服务器。

这种哈希取模算法,也存在因扩容、缩容导致路由不一致的问题。那么应该也需要记住一致性哈希算法的原理去处理。

个人觉得,处理这种session会话使用分布式缓存方案会比较好,服务器保持无状态。

应用六:数据分片

通过哈希算法对处理的海量数据进行分片,多机分布式处理,可以突破单机资源的限制。

应用七:分布式存储

使用一致性哈希算法原理,解决分布式缓存系统扩容、缩容导致数据迁移的问题。(分布式缓存中,节点挂掉后,可以直接让这部分缓存失效即可,不必做数据迁移)