什么是哈希?
我们常常都能听到一些名词,哈希表
、哈希函数
、哈希冲突
、哈希算法
、一致性哈希
,那么这些到底是什么鬼?
首先我们要明白哈希的由来,为什么会有这个东西:
我们通常查找数据的方式有很多种,例如使用树,它们的查找都是先从根节点进行查找,从节点取出数据或索引与查找值进行比较,等等有很多方式,那么,有没有一种函数H
,根据这个函数和查找关键字key
,可以直接确定查找值所在位置
,而不需要一个个比较。这样就预先知道key所在的位置,直接找到数据,提升效率。
即地址index = H(key)
说白了,hash函数就是根据key计算出应该存储地址的位置,而哈希表是基于哈希函数建立的一种查找表,这种方式是不是和数组按下标查找数据
有点类似,数组的空间都是连续的,我们只要知道它的元素的下标,就可以在地址偏移对应的长度,找到地址中对应的数据。但是数组只是一种简单的结构,他的key(下标),只能是0, 1,2, 3。…n,如果我们想通过一个字符串为key,找到这个key所在地址中对应的的其他数据怎么办?于是就有hash的概念产生,你也可以把它简单的理解一种映射关系
!
什么是哈希函数?
key要如何映射到value,这就用到了哈希函数
,也就是说哈希函数决定了,其实key到vlaue的映射关系和逻辑。
常用HASH函数:
-
1.
直接寻址法
。取关键字或关键字的某个线性函数值为散列地址。即H(key)=key
或H(key) = a·key +b
,其中a和b为常数(这种散列函数叫做自身函数) -
2.
数字分析法
。分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。 -
3.
平方取中法
。取关键字平方后的中间几位作为散列地址。 -
4.
折叠法
。将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。 -
5.
随机数法
。选择一随机函数,取关键字作为随机函数的种子生成随机值作为散列地址,通常用于关键字长度不同的场合。 -
6.
除留余数法
。取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生碰撞。
什么是哈希表?
简单来说,就是存储key和value的映射关系的一个表,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。存放记录的数组叫做哈希表。
哈希表有以下几点需要注意:
-
若关键字为
k
,则其值存放在f(k)
的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f
为哈希函数,按这个思想建立的表为散列表。 -
若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少冲突。
举例:
我们把英文字母在字母表中的位置序号作为该英文字母的内部编码。例如K的内部编码为11,E的内部编码为05,Y的内部编码为25,A的内部编码为01, B的内部编码为02。由此组成关键字“KEYA”的内部代码为11052501,同理我们可以得到关键字“KYAB”、“AKEY”、“BKEY”的内部编码。之后对关键字进行平方运算后,取出第7到第9位作为该关键字哈希地址,如下图所示:
上面举例的哈希函数时间上是平方取中法
,真正存储的哈希表实际上只包含了key和value两部分:
关键字 | H(k)关键字的哈希地址 |
---|---|
KEYA | 778 |
KYAB | 795 |
AKEY | 265 |
BKEY | 315 |
什么是哈希冲突?
对不同的关键字可能得到同一哈希地址,即k1≠k2
,而f(k1)=f(k2)
,这种现象称为冲突(英语:Collision)。也就是说,哈希函数算出来的地址被别的元素占用了,也就是,这个位置有人了。好的哈希函数会尽量避免哈希冲突。
如何解决哈希冲突?
1.开放地址方法
开发地址法的做法是,当冲突发生时,使用某种探测算法在散列表中寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到。按照探测序列的方法,一般将开放地址法区分为线性探查法、二次探查法、双重散列法等。
这里为了更好的展示三种方法的效果,我们用以一个模为8的哈希表为例,,采用除留余数法,即哈希函数为:f(key) = key % 8
,往表中插入三个关键字分别为26,35,36的记录,分别除8取模后,在表中的位置如下:
这个时候插入42,那么正常应该在地址为2的位置里,但因为关键字26已经占据了位置,所以就需要解决这个地址冲突的情况。
接下来介绍开放地址方法大常见三种方法:
-
(1)线性探测 。按顺序决定哈希值时,如果某数据的哈希值已经存在,则在原来哈希值的基础上往后加一个单位,直至不发生哈希冲突。
fi=(f(key)+i) % m ,0 ≤ i ≤ m-1
探查时从地址 d 开始,首先探查 T[d],然后依次探查 T[d+1],…,直到 T[m-1],此后又循环到 T[0],T[1],…,直到探查到有空余的地址或者到 T[d-1]为止。所以在插入42时,探查到地址2的位置已经被占据,接着下一个地址3,地址4,直到空位置的地址5,所以39应放入地址为5的位置。
线性探测法的缺点:需要不断处理冲突,无论是存入还是査找效率都会大大降低。 -
(2)再平方探测。按顺序决定哈希值时,如果某数据的哈希值已经存在,则在原来哈希值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。
fi=(f(key)+di) % m,0 ≤ i ≤ m-1
探查时从地址 d 开始,首先探查 T[d],然后依次探查 T[d+di],di 为增量序列12,-12,22,-22,……,q2,-q2 且q≤1/2 (m-1) ,直到探查到 有空余地址或者到 T[d-1]为止。所以插入42时,探查到地址2被占据,就会探查T[2+1^2]
也就是地址3的位置,被占据后接着探查到地址7,然后插入。
-
(3)伪随机探测。按顺序决定哈希值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来哈希值的基础上加上随机数,直至不发生哈希冲突。
fi=(f(key)+i*g(key)) % m (i=1,2,……,m-1)
其中,f(key) 和 g(key) 是两个不同的哈希函数,m为哈希表的长度,双哈希函数探测法,先用第一个函数 f(key) 对关键码计算哈希地址,一旦产生地址冲突,再用第二个函数 g(key) 确定移动的步长因子,最后通过步长因子序列由探测函数寻找空的哈希地址。
比如,f(key)=a 时产生地址冲突,就计算g(key)=b,则探测的地址序列为 f1=(a+b) mod m,f2=(a+2b) mod m,……,fm-1=(a+(m-1)b) % m,假设 b 为 3,那么关键字42应放在 “5” 的位置。
2.链式地址法(HashMap的哈希冲突解决方法)
对于相同的哈希值,使用链表进行连接。使用数组存储每一个链表。
优点:
- (1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
- (2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况
- (3)开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
- (4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
缺点:
- 指针占用较大空间时,会造成空间浪费,若空间用于增大散列表规模进而提高开放地址法的效率。
3.建立公共溢出区
建立公共溢出区存储所有哈希冲突的数据。
4.再哈希法
对于冲突的哈希值再次进行哈希处理,直至没有哈希冲突。
哈希表的查找?
查找过程和造表过程一致,假设采用开放定址法处理冲突,则查找过程为:
对于给定的key,计算hash地址index = H(key)
如果数组arr【index】的值为空 则查找不成功
如果数组arr【index】= key 则查找成功
否则 使用冲突解决方法求下一个地址,直到arr【index】== key或者 arr【index】==null
哈希表的查找性能?
一些关键码可通过哈希函数转换的地址直接找到,另一些关键码在散列函数得到的地址上产生了冲突,需要按处理冲突的方法进行查找。产生冲突后的查找仍然是给定值与关键码进行比较的过程。所以,对散列表查找效率的量度,依然用平均查找长度来衡量。
查找过程中,关键码的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
- 哈希函数是否均匀;
- 处理冲突的方法;
- 哈希表的装填因子。
散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度
α是散列表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。
哈希算法是什么?
根据哈希的定义,便衍射出了很多哈希算法,哈希算法其实和加密有点类似,都是给你一串输入,在返回一串输出,然后输入和输出都一点的联系。
哈希和加密区别:
哈希是真正不可逆的,加密是可逆的,例如密码加密成密文之后,还需要解密成明文,这就是可逆的,而哈希是无法通过输出结果推出输入结果,即不可逆。
常见的哈希算法:
⑴ MD4
- MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年设计的,MD 是 Message Digest的缩写。它适用在32位字长的处理器上用高速软件实现–它是基于 32 位操作数的位操作来实现的。
⑵ MD5
- MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组,其输出是4个32位字的级联,与MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好
⑶ SHA-1 及其他
- SHA1是由NISTNSA设计为同DSA一起使用的,它对长度小于264的输入,产生长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1设计时基于和MD4相同原理,并且模仿了该算法。
哈希算法的用途:
⑴文件校验
- 我们比较熟悉的校验算法有奇偶校验和CRC校验,这2种校验并没有抗数据篡改的能力,它们一定程度上能检测出数据传输中的信道误码,但却不能防止对数据的恶意破坏。MD5Hash算法的"数字指纹"特性,使它成为目前应用最广泛的一种文件完整性校验和(Checksum)算法,不少Unix系统有提供计算md5 checksum的命令。
⑵数字签名
- Hash算法也是现代密码体系中的一个重要组成部分。由于非对称算法的运算速度较慢,所以在数字签名协议中,单向散列函数扮演了一个重要的角色。对Hash 值,又称"数字摘要"进行数字签名,在统计上可以认为与对文件本身进行数字签名是等效的。而且这样的协议还有其他的优点。
⑶ 鉴权协议
- 如下的鉴权协议又被称作挑战–认证模式:在传输信道是可被侦听,但不可被篡改的情况下,这是一种简单而安全的方法。
MD5、SHA1的破解
2004年8月17日,在美国加州圣芭芭拉召开的国际密码大会上,山东大学王小云教授在国际会议上首次宣布了她及她的研究小组的研究成果——对MD5、HAVAL-128、MD4和RIPEMD等四个著名密码算法的破译结果。2005年2月宣布破解SHA-1密码。
参考:
https://baike.baidu.com/item/%E5%93%88%E5%B8%8C%E8%A1%A8/5981869
https://blog.csdn.net/u011109881/article/details/80379505
https://baike.baidu.com/item/Hash/390310?fromtitle=%E5%93%88%E5%B8%8C&fromid=24086145&fr=aladdin
https://www.cnblogs.com/yeya/p/9845910.html