【数据结构】哈希(Hash)

1.什么是哈希

哈希也叫散列,因而哈希表也叫散列表。通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希函数。采用散列函数将记录存储在一块连续的存储空间中,这块连续的存储空间称为哈希表。所得的存储地址称为哈希地址。

2.哈希的常见构造方法

2.1.直接定址

取关键字的某个线性函数为散列地址。即H(key)=key或者H(key)=a*key+b(a,b为常数)。
假设有数组[‘A’,‘B’,‘D’,‘F’,‘D’,‘E’,‘F’,‘C’],求该字符数组里每个字符的出现次数。利用ASCII码,哈希函数可以通过直接定址法H(key)=key-‘A’(ASCII码65),这样每一个key都可以将它的H(key)值,将其当成数组下标放在一个长度为26的int数组中。array[0]表示字母A出现的次数,array[1]表示字母B出现的次数。

2.2.数字分析

处理关键字位数比较多、数字出现频率不高的情况,通过抽取关键字的一部分进行操作,计算哈希存储位置。
假设在记录员工的生日,如20000618。若直接把年月日作为哈希地址,空间浪费很大,若是将月日作为哈希地址,则既能减少空间浪费又能降低哈希冲突的可能性。

2.3.平方取中

当无法确定关键字中哪几位分布较均匀时,可以求关键字的平方值,然后取平方值的中间几位作为散列地址。

2.4.除留取余

最常用的构造哈希函数方法。哈希函数为:
H(key) = key mod p
其中mod为取余运算,p 此方法不仅可以对关键字直接取模,也可以在折叠、平方取中之后再取模。

2.5.折叠法

适用于关键字位数很多,而且关键字每一位上数字分布大致均匀的情况。将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后去这几部分的叠加和舍去进位后作为散列地址。
假设关键字1472580369,表长为3。可将关键字分成4部分,147|258|036|9,147+258+036+9=450,取450为散列地址。

2.6.随机数

选择一个随机数,取关键字的随机函数值作为他的哈希地址。用于关键字长度不同的场合。哈希函数为:
H(key) = random (key)
其中random是随机函数。

2.7.如何选择

一般我们在考虑选择哪种哈希函数时,会考虑以下几点:

  • 计算哈希地址所需时间
  • 关键字长度
  • 哈希表大小
  • 关键字分布情况
  • 查找频率
    以上方法可以杂糅在一起使用。当然也可以扩展出其他的哈希函数。

3.哈希冲突

不同数据的关键字通过哈希函数得到一个相同的地址,这时就发生冲突,这种冲突一般叫做哈希冲突。把冲突的关键字称为这个哈希函数的同义词。
解决办法常见的有4种:开放地址法、再哈希法、链地址法、公共区溢出法。

3.1.开放地址法-线性探测

一旦发生冲突,就寻找下一个空的哈希地址,只要哈希表足够大,总能找到地址存放。

3.2.开放地址法-二次探测

在线性探测的基础上,多了寻找上一个空的哈希地址的办法。

3.3.开放地址法-随机探测

随机寻找一个空的哈希地址。

3.4.再哈希法

顾名思义,遇到哈希冲突时,换一个哈希函数计算。

3.5.链地址法

将同义词存储在一个单链表中,我们称这种单链表为同义词子表,哈希表中存储同义词子表的头指针。下图中的哈希函数为
H(key) = key mod 13。可以看出1、14、27、79取余结果都是1,哈希表中的地址1保存了1、14、27、79这个链表的头指针。
【数据结构】哈希(Hash)_第1张图片

3.6.公共区溢出法

建立公共区,把哈希冲突的没有存储的同义词集中保留起来。

4.哈希表的优缺点

优点:不论哈希表中有多少数据,插入和删除很快,时间复杂度O(1);查询速度比树快,树的时间复杂度为O(n),因而若不需要有序遍历且数据量大小可预判,哈希表查询很快。
缺点:基于数组的数据结构,难于扩展。因而需要对表中数据量有清楚的认识。

5.哈希函数的使用场景

5.1.负载均衡

根据后端节点的某个固定属性(如来源 ip 、cookie、用户名等)计算哈希值,然后把所有节点计算出来的哈希值组成一个圆环。 请求过来时根据请求的特征计算该特征的哈希值, 然后顺时针查找哈希环上的哈希值,第一个比请求特征的哈希值大的哈希值所对应的节点即为被选中的节点。
【数据结构】哈希(Hash)_第2张图片

5.2.安全加密

常见的加密算法都是哈希函数的实现:MD5、SHA、DES、AES。

5.3.唯一标识

任何文件在计算中都可以表示成二进制串。我们若是想要比对两个文件相等,可以给每个图片取一个唯一标识(信息摘要)。通过哈希函数得到一个哈希值,用它作为图片的唯一标识。用唯一标识来判定图片是否在图库中。

5.4.数据校验

在P2P下载协议中,我们会从不同的机器上下载同一部电影的不同部分,然后在自己的机器上将电影组装起来。如何知道这其中某个部分的电影下载过程中出了错误或者内容被篡改?先对所有部分进行哈希计算,并保存在种子文件中。等到所有部分下载完成,我们对所有部分进行哈希计算得到哈希值,再和种子文件中的进行比较,以此来校验文件是否完整。

5.5.数据分片

处理海量数据的时候,单台服务器无法加载和计算如此海量的数据,需要将海量数据均匀地分给n台服务器进行并行计算。可以使用除留取余哈希函数,对服务器个数n取模,相同结果的数据会被分到相同的服务器上,交给这台服务器处理。实现并行处理海量数据,最终再将结果合并起来。

5.6.分布式存储

为提高数据读、写能力,一般采用分布式存储,比如分布式缓存。因为大量数据要缓存,所以需要将数据分布在多机。原理同上面的数据分片。对机器个数n取模,就得到该缓存数据应存储的机器编号。
若是m个机器无法支撑,扩容到了m+n个机器,原来的哈希值都失效,需要重新计算,这将会导致穿透缓存直接请求数据库。
这时候可以使用一致性哈希函数。
假设我们有k个机器,数据的哈希值的范围是[0, MAX];将[0, MAX]划分为m个小区间(m远小于k),每个机器负责m/k个小区间。当新机器加入的时候,我们就将某个小区间的数据从原来的机器中搬移到新的机器中。这样,既不用全部重新哈希,也保持了各个机器上数据数量的均衡。
假设用户A的记录落在了D1机器,用户B的记录落在了D2机器,用户C的记录落在了D0机器上。哈希函数是H(key) = key mod 3。假设我们增加了一台机器,哈希函数需要变成H(key) = key mod 4。这就会出现用户明明存在记录但是却查不到的情况,如果重新哈希代价很大。那么一致性哈希是怎么做的呢?
一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,假设哈希函数的值空间为0到232-1。从0到232-1代表的分别是一个个的节点,这个环也叫哈希环。我们将我们的节点进行一次哈希,按照一定的规则,让节点落在哈希环上。
【数据结构】哈希(Hash)_第3张图片
我们约定,通过数据key的哈希值落在哈希环上的节点,如果命中了机器节点就落在这个机器上,否则落在顺时针直到碰到第一个机器。A的哈希值落在了 D2 节点的前面,落在了D2机器上,D的哈希值在D1节点的前面,落在了D1机器上,B的哈希值刚好落在了D1节点上,其它依次类推。
当机器减少或增加的时候,假设D2因为一些原因宕机了,只有数据A的记录需要重新定位存储到节点D1上,其它的数据都没有被影响到;当我们需要增加一台机器D4,在D2和D1之间时,之前落在D2到D4之间的数据都需要重新定位到D4上面。

你可能感兴趣的:(数据结构,哈希算法,数据结构,散列表)