u8,u8国际,u8国际官方网站,u8国际网站,u8国际网址,u8国际链接,u8体育,u8体育官网,u8体育网址,u8注册,u8体育网址,u8官方网站,u8体育APP,u8体育登录,u8体育入口
散列函数(英语:Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。
与都提及了一个共同的概念:哈希函数(散列函数)能够将任意长度的输入值转变成固定长度的值输出,该值称为散列值,输出值通常为字母与数字组合。
在对哈希函数有了一个定义层面的基本理解后,我在下方列举了整理后的哈希函数属性列表:
定义里面讲的是哈希函数接收任意长度的输入,但在实际实现中,大家都会指明一个具体可接收的阈值,例如
最高接受(2^64-1)/8长度的字节字符串。此处需要理解的是哈希函数拥有较为庞大的输入值域,它
,即对于输入值x,与哈希函数fn,无法求出一个值y,使得x与y的哈希值相等。但是由于上文我们知道,由于哈希函数实际上代表着一种映射(对应关系),将一个大区间上的数值映射到一个小区间上,它一定是有冲突的,这里的无冲突同样是指“
,正向地计算很容易,但是反向的计算在当前的计算机能力条件下做不到,但是这种冲突的概率发生了怎么办我们后面再说。
手机电话簿我们都知道,里面的人名依据首字母从a-z的顺序排列着的,这种排列方式能让我们快速地找到某个人,如下图所示:
我们不妨认定这种映射过程为一个简陋的哈希函数,并称这个简陋的哈希函数为“电话簿哈希”。我们可以看到,“电话簿哈希”的运行机制很简单:对于任意输入值,“电话簿哈希”都取第一个字的拼音首字母输出。
这个哈希函数实际上满足我们在上方列举的8条属性中的前5条的。但是从第6条开始,我们定义的这个电话簿哈希就不满足了。
我们来分析一下,由于这个这个函数过于简陋,它的冲突概率是较高的,比如我们分别输入“张三”、“章五”,“电话簿哈希”都输出了“z”,对于这种冲突,在哈希函数具体实现中处理方法有多种,例如“链地址法”、“再哈希法”等,文章也有很多,需要理解的是为啥它们要这么做,好处都有啥,此处不谈。
回到我们的电话簿哈希中,针对此处的冲突我们修改一下哈希函数:“将取第一个字的首字母改为取每个字的首字母”。也就是说输入“张三”与“章五”输出值变成了“zs”与“zw”,之后我们再来通过输出值执行名字到电话簿位置的映射。
存放到具置遵循以下规则,定位依然遵循从a到z的顺序,但是出现冲突后取第二个字的首字母再排序。那么咱们这个升级后的“电话簿哈希”对于上方的输入,就有了更为具体的电话簿位置映射。
此时,“张三”与“章五”无需冲突在位置“z”上,而是在位置“z”上冲突后,执行了冲突解决方法,即以第二个字首字母对冲突对象再排序,“s”排在“w”之前,因此,“张三”与“章五”应当排在电话簿的“z”下,且“张三”的位置在“章五”前面。
但是这个升级后的哈希函数依然有漏洞,它还有冲突未解决,但是此处主要为了说清楚哈希函数解决冲突的一个大致套路与思想,这个哈希函数的漏洞由读者(然而一个读者也没有)自行发掘,不妨碍此处的说明。
即使我们已完美解决了冲突的问题,但是回顾咱们“电话簿哈希”这个哈希函数的设计原理,咱们的电话簿哈希依然存在问题——假设我姓“刘”,那么由于我会保存很多姓“刘”的亲戚,电话簿中大量的联系人都映射在了“L”这个地址下。
即使我们有了升级版的解决办法,能解决冲突的问题,但是由于映射的不均匀,大量的数据堆积在了一块区域,那么冲突发生的概率顿时就高了许多。由此导致本来查找速度极快的哈希函数速度降下来许多了——因为它需要继续执行冲突之后的遍历。这个与现实生活中的体验是一致的:我们能通过一次查找定位人名在“L”下,但是我们要具体找到目标甚至需要遍历“L”下挂着的所有对象,这是一个优秀的哈希函数所难以容忍的。
我们的哈希函数“电话簿哈希”还需要升级,使得映射能够更均匀一些。这里就不再继续开脑洞如何升级“电话簿哈希”这个函数了。但是需要说明的是,就算我们将来能解决这个映射不均匀的问题,我们也会引入一个新的问题:这个哈希函数计算起来变复杂了,它为了兼顾前文我们列举的种种要求种种属性,函数考虑的东西越来越多应对的情况越来越丰富,以至于函数本身体积膨胀,计算哈希值(输出值)变的很耗费CPU(脑细胞)。
试想,我们发明这个哈希函数目的就是为了快点找到目标人名,但是在找到这个人名之前我们得经过一系列的计算,那岂不是违背设计这个函数以达到“快速查找”的初衷。所以这里往往要做一个权衡,实际上很多类似的函数设计都有这个权衡在里面。
手机中的电话簿实现到了这一步基本就停止了,没有再去解决映射不均匀的情况实际上也是权衡的结果。人脑根据拼音首字母来定位到特定字母目录下,而不用遍历一整个电话簿已经帮了很大忙了,而且人脑完成这一映射的速度也非常快(计算简单),所需要的仅仅是费些时间遍历某个字母下挂靠着的节点而已。
哈希函数的通俗说明到这里已经说的差不多了,上文列举了哈希函数所具备的属性,也通过一个通俗的例子来辅助理解了哈希函数的种种属性与用于“快速查找”的应用场景。哈希函数十分强大,除了上文的“快速查找”场景以外,还有许多经典的场景。以下是从抄过来的关于哈希函数的应用场景。
,散列值可用于唯一地识别机密信息,这个应用场景是基于属性“无冲突”的特性,但是本文关于无冲突还是太浅显,实际上各种散列函数为了实现更强的抗碰撞,都有各自更加复杂的设计。
”的内容。人们通过哈希函数获得“指纹”(摘要),打包原文与“摘要”一同发送,接收者只需要将原文进行一次哈希后与“摘要”进行匹配即可。那么如果有人将“摘要”与原文配套修改了怎么办,那我岂不是被欺骗了?实际上摘要的安全性一般还要由加密来保证,关于公钥私钥的加密解密的内容好文也有很多,此处超纲不谈。
,这应当是最经典的用法了,在学校我们学习散列表时都会提及,散列表是数组加链表的组成,上文我们举的关于“电话簿”的例子其实就类似于散列表的定义。
上方引用来自下的最后一句,对于这句引用代入到例子中同样好理解,在我们所举的“电话簿”例子中,假设电话簿中所存名字都是“刘”姓,那么所有的映射都集中到了“L”地址下了,所有的姓名挂靠在“L”地址节点下,整个哈希表退化成了一个链表,那么链表的查询能力大家是有目共睹。因此,一个优秀的散列函数对于查询是至关重要的。
,这个概念同样很好理解,假设小A给小B发送一封信件,由于此信件过大,发送的时候需要拆分成三分标序后发送,到达目的地再行组装。那么,如何保证组装的数据顺序没错呢?如何保证数据发送的过程中没有丢包呢?我们只需要携带一份原完整信件的哈希值(摘要)一同发送即可,到达目的地的数据拼装好后,调用哈希函数进行一次哈希,将得到的数据与摘要进行匹配,无误则说明数据完整。
,这个就触及到笔者的知识盲海了,为了不误人,附上原文截图一份。