1. 哈希函数的定义及性质:
1)哈希函数是函数,所以接收一个变量,返回一个值,接收的变量,其定义域理论上是无穷大,返回的值是哈希值,也就是每个变量都能生成对应的哈希值
2)哈希函数的值域是有穷的(哈希值有穷个),并非无穷大,哈希函数相当于把无穷大的范围中的所有变量映射到有穷范围中
3)对于同一个变量,哈希函数输出的哈希值不会发生变化,例如 f(1)=0,那么把 1 重新传入 f(x),f(1) 还是 0
4)不同变量对应的哈希值可能会相同,称为哈希碰撞。无穷映射到有穷,这一点是显然的。
5)最重要的性质,即便会发生哈希碰撞,但如果传入很多个不同变量,其哈希值是均匀分布在值域中的,这就是哈希函数的离散性。例如输入是 0~98,输出是 0 1 2,那么每个输出基本上对应 33 个输入,这就是均匀分布。
6)如果哈希函数的输入存在一定的规律性,哈希函数不会捕捉这种规律,哈希值和输入规律无关。从这个角度说,哈希函数可以打乱输入。
7)如果哈希函数再次映射,那么映射之后的空间仍具备均匀分布的性质。例如,无穷大映射到 0~98,这是第一次映射,哈希值范围是 0~98,均匀分布。然后第二次映射,0~98 中的哈希值,每次 %3,将哈希值再次映射到 0 1 2 中,那么这个 0 1 2 上,哈希值仍然均匀分布。
2. 如何通过一个哈希函数,改出来 1000 个哈希函数?
一个哈希函数得出来的哈希值,哈希值中的每个数都是相互独立的。例如 16 位的哈希值,每一位都相互独立。此时可将哈希值分割为前 8 位和后 8 位,前 8 位看作是 h1,后 8 位看作是 h2,新的哈希函数可借助 h1 和 和 h2 产生,比如 h1+1*h2 就是第一个哈希函数,h1 + 2*h2 就是第二个哈希函数,依次类推,且这些哈希函数仍然独立。如果不拆分哈希值,也可以找两个哈希函数,f(x) 和 g(x),之后对这两个哈希函数进行组合,f(x) + g(x),f(x) + 2g(x),依次类推,也能创造出 1000 个哈希函数。(话说,独立的两个函数线性组合,且组合函数仍然独立,这证明呢?)
3. 哈希表的大致逻辑
首先创造出容量 17 的哈希表(0~16),然后传来一个 key, 这个 key 经过哈希函数的运算生成哈希值,然后哈希值 % 17,生成 0~16 上的某一个值,这个值对应的就是哈希表中的位置,此时再将 key,value 挂载到该位置上。显然,一定会发生哈希冲突,即两个 key 映射到了同一位置。解决方法是在先前的 key value 对后面接链表。例如图中 10 位置处的(A, 17)和(zuo, 31),就算利用链表连接的。
4. 哈希表的扩容
首先,哈希表中的各个位置上挂载的键值对也是均匀分布的,这意味着一旦某个位置挂载的键值对数量为 h,其余位置的键值对数量也是 h 或者很接近 h。如果键值对挂载过多,哈希表的查询效率会下降,所以需要在键值对达到一定数量的情况下对哈希表进行扩容,保证其查询效率。假设某个位置键值对长度为 5 时哈希表就需要进行扩容,具体扩容步骤如下:
1)重新创建一个哈希表,比如 104 的容量
2)原先哈希值经过 % 17 放如原始哈希表中,现在哈希值需要经过 % 104 放入扩容的哈希表中,即每个键值对需要用哈希函数计算一次。
那为什么说哈希函数的扩容代价是 O(1) 呢?
首先,这里说的是扩容代价,不是计算哈希函数的代价。扩容代价是指:假设数据总数为 N,从某一个容量开始,增长到 N 所需要的代价。例如从某一容量开始,每次扩容一倍,那么扩容代价就是 O(logN) (以 2 为底);如果每次扩容5倍,那么扩容代价就是 O(log(5)N) (以 5 为底)。但这样,哈希函数扩容代价仍不是 O(1)。需要注意到的一点是,扩容这件事一旦发生,在短时间内很大概率不需要进行第二次扩容,从长远角度看,扩容代价可以被压的很低。但这样也不足以说明哈希函数的扩容代价为 O(1)。
实际上,从数学角度来说,扩容代价不可能为O(1),一定会存在扩容代价。但在实际使用时,是可以将其看做O(1)的。原因在于扩容可以离线扩容。如下图所示,左边是原始哈希表,右边是新哈希表,在用户使用时,put 和 get 操作都是在原始哈希表中进行的,即使原始哈希表此时需要扩容。而新哈希表也会将用户的 put 操作执行一遍,同时将原始哈希表中的各个值重新存入到新哈希表中。等到原始哈希表的值被新哈希表全部重新计算后,此时再将新哈希表给用户进行使用。所以对于用户来说,他读取和存入数值时,永远是O(1)操作。即便新哈希表替代了原始哈希表,但是这个替换过程用户是感受不到的,用户面对的永远是一个完整的哈希表。
具体到各个语言时,又会有所区别,例如 JVM 中的哈希扩容。注意 JVM 中哈希结构不是桶+链表,而是桶+红黑树(平衡搜索二叉树),即相同位置处的元素都是利用红黑树连接的。
5. 一道经典的利用哈希函数性质的大数据题目,有个大文件 100T,其中文件每一行都是一个字符串,现在要统计整个文件中每种相同字符串的数量,如何去做?
此时,需要问面试官,可以使用多少台机器,假设是 1000 台。那么此时就可以将大文件每一行的字符串进行哈希运算(%1000),放入到 1000 台机器中。哈希函数的一个重要性质是相同输入必然会产生相同输出,若某两行的字符串完全相同,则该字符串一定会分配到一台机器上,此时再在那台机器上进行统计即可。如果子机器的任务量还是太大,可以在子机器上继续利用哈希函数进行任务的分流。在大数据题目中,哈希函数用的非常多,就是因为这个性质可以将任务进行分流,相同输入必然会导致相同输出。