redis中几种哈希函数的研究
这段时间一直在研究redis的源码,在研究其字典结构体的实现部分时,遇到了几个陌生的哈希函数。研究了一下才发现这些函数在如今的开源项目中早已得到了广泛的应用,如标准C++库、nginx、Hadoop等等。
于是乎,我对redis中的这几个函数做了点研究,自己琢磨出点东西来,在这里与大家分享一下。我写这篇文章的目的主要是想阐述一下我对哈希函数的看法,同时描述一些原理性的东西,因为我发现网上这方面的文章还比较少。
首先我们来看一下什么样的函数才能作为哈希函数。
一、哈希函数的构建准则
大神Thomas Wang认为,一个好的哈希函数必须具备以下两点特性:
1、一个好的哈希函数应该是可逆的。即,对于哈希函数输入值x和输出值y,如果存在f(x) = y,就一定存在g(y) = x。说白了,就是哈希函数可以将某一个值x转换成一个key,也可以把这个key还原回成x。
2、一个好的哈希函数应该容易造成雪崩效应。这里的雪崩效应是从比特位的角度出发的,它指的是,输入值1bit位的变化会造成输出值1/2的bit位发生变化。
首先,我们看第一条特性,这与我们通常认为的哈希函数的特性不符,一个哈希函数为什么要做到可逆呢?一般只有加解密算法才涉及到可逆性的讨论啊。经过思考,我的结论是,具有可逆性的哈希函数可以从根本上消除哈希过程中的冲突(collisions)。原因显而易见,因为函数具有可逆性,所以输入值与输出值一定一一对应,一个输入只能产生一个唯一输出,不可能存在f(x1) = y, f(x2) =y 且 x1 != x2的情况。
同时,这样的函数存在的问题也显而易见,因为输入值与输出值的数量相同,所以面对大量输入时,会面临存储空间不足的问题。我认为Thomas Wang的想法应该是这样,哈希函数应只负责将输入值尽量均匀的分布在某一空间,而不管实际的物理内存是否可以容纳该空间。将这一问题留给具体的使用者。对于内存不足的情况,一般的处理方法是对哈希结果进行二次映射,将这些值存入到一个固定大小的物理内存块中。具体映射的方法有很多,最简单的是取余运算,但是取余的方法方法过于耗时,可以通过一个小技巧避免。我们可以将放置哈希结果的物理内存块的大小设置成2的n次方的形式,此时, tablesize = 2 ^n,key_addr =hash_value % tablesize = hash_value & (tablesize - 1)。这里用位运算来代替取余运算,在tablesize = 2 ^ n 的情况下,两者的效果相同。
其次,我们再来看一下雪崩效应特性,这条特性的主要目的是使得哈希结果更为离散均匀。
那么,如何才能实现一个可逆的具有雪崩效应的哈希函数呢?
在分析了各种哈希函数的具体算法后,我发现,这些算法都是通过一系列可逆的、具有雪崩效应的运算组合实现。
那哪些运算才是可逆的、具有雪崩效应的呢?这就是我们第二章要将的内容,运算组合的可逆性与雪崩效应。
二、运算组合的可逆性与雪崩效应研究
因为之后的各类哈希算法中出现的基本上都是一些加、乘、位移、异或操作的组合,初看这些算法估计没人知道他到底想干啥,算法本身也没什么注释,如果直接上算法各位同学可能不好理解。所以这里我想先说一下这些运算组合的内在机理。
下面,我们分别来看一下各种运算组合与可逆性、雪崩效应这两条特性之间的关系。
1、可逆性研究
可逆性这东西比较难理解,毕竟不是学算法的,网上有没这方面的资料,只能自己想了,这里的分