浅谈哈希

大家熟悉的哈希qwq

哈希

哈希在我的理解里就是,一一对应!
比如我们把某个字符串A,对应为某个数字x鸭,某个字符串B,对应为某个数字y鸭;把某个图片对应为某个数字鸭,之类der……
根据这个特性, 理想的哈希应该能满足一些很重要的特性:

  • 对应是不变的,如A不管什么时间什么地点,永远对应的是x;
  • 对应是不冲突的,如只有A可以对应x,其他字符串不能对应到x;

第一个特性很好得到,只要我们的哈希函数确定,显然无论什么时候,只要哈希函数不变,就会一直对应。
但是第二个特性就不那么好做到了,只能存在于理想中了……

哈希冲突

我们把不同的字符串,对应到了同一个位置/数字,称之为哈希冲突。
对应哈希冲突,有一些常见的解决方法。
(我个人认为,发生哈希冲突时,首先要着眼的是哈希函数的设计,如果哈希函数设计不当,哈希甚至会退化成线性表(画面太美,不敢看))

教科书中的解决方案

我们首先把问题简化一下,看做是将某个数据哈希到array[hashcode]中,换句话说,就是拿数组下标当哈希值,里面存放对应的内容。

线性探测法

对于哈希冲突,比如A和B,我们将array[hash(A)] = A。此时放入B,但是hash(A) == hash(B)
这种情况下,我们不断的去寻找,空着的位置在哪里……
因为这里array[hash(B)(也就是hash(A))已经有人住了,所以我们就在这个数组下标中一个一个往后寻找,直到找到一个空位,安插进去,就是B所在的位置啦。
这样做很多缺点:

  • 有集聚效应,当发生冲突后,因为占用了别人的位置(那个位置可能是hash(C)的位置阿喂!),所以之后可能会很频繁的发生冲突。
  • 难以删除,因为很可能一串都是同一个哈希数的家,所以中间删除一个以后,后面的可能会难以查找了。因此只能对删除的元素打标记,但是这样又可能导致空间浪费……

为了解决这些问题,也有一些方案。比如发生冲突时,我们不再是一个一个向后寻找,而是隔几个向后寻找……或是对不同的hash值,跳跃的个数不同……以此来避免上述的一些问题。

再哈希法

顾名思义,发生冲突后,对该元素使用另一个哈希函数,再一次哈希以确定应该放置的位置。

拉链法

对于每一个数组位置,放置的元素相当于一个链表。
当有冲突时,我们将这个元素插入到链表尾部,以此来避免冲突。

哈希中的装载因子

哈希中,我们的数组有多少个,相当于我们有多少个bucket,装载因子是:总键值对数/箱子个数 ,它体现了哈希表的【满】的程度。
该数值越大,意味着哈希表越满,越容易导致冲突,性能也就越低。
大多哈希的实现,当装载因子达到0.75以上后,一般就会自动扩容。
哈希扩容中,一般会创建两倍大小的bucket,再将之前所有的数据,进行再次哈希,装载到新箱子中。
这里容易成为性能瓶颈,一方面,可能某个用户一来,恰好进行了扩容操作,那么对于该用户来说,是不公平的(为什么我等待时间这么长5555);另一方面,所有哈希值可能都没用,也太弟弟了!

在这里有一个结论: 扩容扩两倍,原有的哈希值发生变动的可能和情况最少!

一致性哈希

为了避免可能的再次哈希情况,引入了一致性哈希。
一致性哈希也经常用于分布式实现中。

我们把哈希值唯一确定,之后映射到一个圆环中。
那么,当分布式出现崩溃或新增节点的情况时,只会影响圆环中的一部分。

这样可能会出现没有考虑到不同节点的性能不同的问题,或者负载不够均衡的问题。
我们可以进一步引入虚拟节点,再把多个虚拟节点向实际节点映射,完事~。
(感觉计算机很多方面的问题,都可以通过引入抽象层解决)

参考资料



本文标题:浅谈哈希

文章作者:Xie Keyi

发布时间:2019年01月02日 - 20:01

原始链接:https://xiekeyi98.com/819591f7.html

许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 转载请保留原文链接及作者。