前几天和一个大佬交流了几个问题,其中一个关于ID生成的问题推展到了哈希冲突和一个与之相关的一个数学趣题生日悖论。
当时对于两个事情的理解不够深刻,周末花时间仔细研究了一下,发现很有趣,于是觉得写一篇文章来和大家分享,今天的主题就是哈希冲突和生日悖论。
通过本文你将了解到以下内容:
哈希的本质就是数学,简单来说哈希函数实现了各种长度和形式的输入经过公开的哈希函数的运算生成一个固定长度的串,并且这个过程是单向不可逆的,也就是无法从哈希生成的串逆转为最初的输入。
听起来确实很神奇且有用,像一个万能胶囊,在一个小的范围内装了很多不一样的东西,原来有10MB的文件或者1GB的文件经过哈希运算后都会被映射到一个固定长度的串,可见压缩程度之大。
但是又不得不思考另外一个问题:输入是无穷尽的,生成的哈希串长度是固定的,那么必然面临着多个不一样的输入被映射压缩为一个相同的哈希串,就是无限集合映射有限集合导致的哈希碰撞或者叫哈希冲突。
举个栗子:
假如哈希串固定长度是二进制10bit,那么这个10bit空间可容纳的最大数量为2^10=1024,先不说无限输入集合,假如现在有2000个输入,在哈希函数均匀的前提下最少会有2000-1024=976个冲突。
看到这里,肯定有人会说那不用哈希了,但是哈希的压缩映射和不可逆性带来的便利确实有很大的吸引力,所以知难而退并不是个好主意。
我们知道幂次爆炸,所以如果把二进制位数扩展到32bit->64bit->128bit->256bit呢,2^32约为42亿,2^64约为1800多亿亿,128和256就更大了,貌似到了这里开始柳暗花明了,我们将长度增加空间就幂次增加,有效降低了冲突的概率,这也是当前主流的思路和方向。
小结:
哈希函数本身有很多种,底层的实现都有非常复杂的数学逻辑,均匀分布是哈希函数的一个重要特征,笔者才疏学浅并没有对哈希函数的数学原理做过多研究。
无限输入集合向有限集合的压缩映射是必然会出现碰撞的,解决碰撞的一个有效方法是增加映射空间长度,理解这一点就足够进行后面的阅读了,相信聪明的读者一定Get到笔者的意思了。
前面讨论了哈希冲突的必然性,那么我们不禁要思考:那我该选择多少bit的哈希函数才能避免碰撞呢?
其实面对这个问题的时候,我最开始是这么想的:使用32bit的哈希函数这样还有42亿个空间呢,那么产生哈希碰撞岂不是42亿分之一,貌似可以高枕无忧了,先看一个有趣的问题:
图片来自网络
生日悖论是指在不少于 23 个人中至少有两人生日相同的概率大于 50%。例如在一个 30 人的小学班级中,存在两人生日相同的概率为 70%。对于 60 人的大班,这种概率要大于 99%。从引起逻辑矛盾的角度来说,生日悖论并不是一种 “悖论”。但这个数学事实十分反直觉,故称之为一个悖论。
确实有一些违背直觉,一年365天怎么会有那么多人生日相同呢?然而这却是个事实。
来实际算一下这个问题:
假设有n个人在同一班级内,在不考虑特殊因素的前提下,例如闰年、双胞胎,假设一年365日出生概率是平均分布的,计算至少有两个人在同一日出生的概率是多少?
计算分析过程:
我们从其对立事件进行求解,找到n不断变化时对应的概率分布,p(n)表示n个人中每个人的生日都不同的概率,先考虑边界条件当n>365根据鸽巢原理其概率为0,n ≤ 365,则概率为:
在维基百科也有通用计算公式,一起看下:
再看下这个曲线和具体的离散化数据:
图片来自 维基百科
图片来自 维基百科
结论:
人数n=23时至少有两人生日相同的概率是50.7%,n=30时概率是70%,n=50时概率是97%,这么看来确实有悖直觉,但是这就是事实。
我们暂且以32位长度的CRC32算法为例来描述哈希碰撞的可能性。
简单来想CRC32的空间大小是42亿,但是实际上并不代码40亿左右数据才会出现碰撞,事实这个数据规模并不需要很大就会出现碰撞。
CRC32的碰撞问题本质上可以从生日悖论的角度来分析,相当于计算在有N个输入的情况下出现碰撞的概率。假设现在有K个输入,不出现冲突的概率计算(将42亿用S表示):
这个计算过程和生日悖论基本是一样的,随着K的增加这个概率值下降非常快,笔者在网上找了一份CRC16-CRC64的冲突测试报告,可以看下:
//http://www.backplane.com/matt/crc64.html output.16 Count 18134464/18200000 output.17 Count 18068928/18200000 output.18 Count 17937856/18200000 output.19 Count 17675712/18200000 output.20 Count 17151424/18200000 output.21 Count 16103198/18200000 output.22 Count 14061250/18200000 output.23 Count 10770169/18200000 output.24 Count 7092360/18200000 output.25 Count 4153742/18200000 output.26 Count 2259269/18200000 output.27 Count 1179721/18200000 output.28 Count 603421/18200000 output.29 Count 305089/18200000 output.30 Count 153722/18200000 output.31 Count 77254/18200000 output.32 Count 38638/18200000 output.33 Count 19232/18200000 output.34 Count 9652/18200000 output.35 Count 4914/18200000 output.36 Count 2343/18200000 output.37 Count 1204/18200000 output.38 Count 637/18200000 output.39 Count 302/18200000 output.40 Count 152/18200000 output.41 Count 75/18200000 output.42 Count 52/18200000 output.43 Count 21/18200000 output.44 Count 13/18200000 output.45 Count 7/18200000 output.46 Count 1/18200000 output.47 Count 1/18200000 output.48 Count 1/18200000 output.49 Count 0/18200000 output.50 Count 0/18200000 output.51 Count 0/18200000 output.52 Count 0/18200000 output.53 Count 0/18200000 output.54 Count 0/18200000 output.55 Count 0/18200000 output.56 Count 0/18200000 output.57 Count 0/18200000 output.58 Count 0/18200000 output.59 Count 0/18200000 output.60 Count 0/18200000 output.61 Count 0/18200000 output.62 Count 0/18200000 output.63 Count 0/18200000 output.64 Count 0/18200000
输入时1820w随机数据,上述数据给出了CRCx情况下1820w输入产生的碰撞数量,可以看到在CRC32中出现了38638个冲突,在CRC49中才出现0碰撞,所以冲突率还是很高的。