所以什么是哈希?

  • Post author:
  • Post category:其他


引用致谢:


https://zhuanlan.zhihu.com/p/78107140



https://cloud.tencent.com/developer/news/321607



https://www.cnblogs.com/big-devil/p/8590242.html



一、基本概念


一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。

这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数

所以说了这么多,到底什么是Hash呢

这么在网上看到了一个说明,很通俗,这里引用说明:

你很想学太极拳,听说学校有个叫张三丰的人打得特别好,于是你到学校学生处找人,
学生处的工作人员可能会拿出学生名单,一个一个的查找,最终告诉你,学校没这个人,
并说张三丰几百年前就已经在武当山作古了。
可如果你找对了人,比如在操场上找那些爱运动的同学,人家会告诉你,"哦,你找张三丰呀,
有有有,我带你去。于是他把你带到了体育馆内,并告诉你,那个教大家打太极的小伙子就是张三丰',
原来"张三丰.是因为他太极拳打得好而得到的外号。学生处的老师找张三丰,那就是顺序表查找,
依赖的是姓名关键字的比较。而通过爱好运动的同学询问时,没有遍历,没有比较,
就凭他们"欲找太极'张三丰',必在体育馆当中"的经验,直接告诉你位置。

HASH主要用于信息安全领域中加密算法,它把一些不同长度的信息转化成杂乱的128位的编码,这些编码值叫做HASH值. 也可以说,通俗的说

hash就是

找到一种数据内容和数据存放地址之间的

映射关系。



hashCode

在这里插入图片描述

1、hashCode的存在主要是

用于查找

的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;

2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;

3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;

4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

再归纳一下就是hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。以下这段话是从别人帖子回复拷贝过来的:


举例说说

1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
例如内存中有这样的位置
0  1  2  3  4  5  6  7  
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为998的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。

2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以817除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊

JDK中String.hashCode方法:
在这里插入图片描述


二、hashCode在Java中的应用

在上面我们提到过,hash函数算法会有一定的冲突情况,比如在字符串的hashCode计算过程中,会出现两个不同的字符串但hashCode一样的情况,例如:

除了上述两种情况,还有很多冲突的案例,大家可以写代码尝试一下。

那问题来了,既然hashCode并不能完全表示唯一性,那Java中用什么来比较两个字符串,对象呢?hashCode还有什么作用呢?


equals

在Java中,我们比较字符串,比较对象,用的方法常常是,我们摘录equal重点部分
在这里插入图片描述

上面代码就是使用最简单的乘法迭代算法实现获取唯一的hash值

我们看到,equals 对字符串做了严格字符级别比较,可以保证完全相同。

equals v.s. hashCode

由上面的分析,我们得出一下两个结论:

1.相等的两个对象他们的肯定相等,也就是用对比是绝对可靠的。

2.相等的两个对象他们的不一定相等,也就是不是绝对可靠的。

但,如果所有的比较都使用equal去比较,显然效率太低,所以解决方案是:

每当对比的时候,首先用hashCode去对比,如果hashCode不一样,则表示两个对象不相等。如果hashCode相同,此时再对比进行equal比较,如果equal相同就是真的相同。

HashSet, HashMap, HashTable

在Java 容器中(Set,Map,Table),经常会用到key值的检索,所以提供了,,三种具体实现方案,利用上面我们重点标注的解决方案【先hashCode,后equal比较】,可以大大提高对比检索效率。
在这里插入图片描述

在这里插入图片描述


三、哈希表



1.hash表的特性


Hash 表是使用 O(1) 时间进行数据的插入删除和查找,但是 hash 表不保证表中数据的有序性,这样在 hash 表中查找最大数据或者最小数据的时间是 O(N) 。


2.寻址和 hash 函数


理想状态下 hash 足够大,每一数据保存在一个 hash 存储单元内,这样对于插入删除和查找某一个数据就可以直接得到。但是现实情况下 hash 表不可能无限大,而且理论上保存的数据的个数是没有限制的,这样保存的数据的数量就远远大于 hash 表的存储单元的数量。

为了实现在 O(1) 内对数据进行插入删除和查找,就必须将一个数据映射到 hash 表中的固定位置,这个映射函数就是 hash 函数。 Hash 函数通过对数据进行计算得到一个在 hash 表中的位置地址。

在这里插入图片描述

要选择较好的 hash 函数,以及 hash 表存储单元的数量,这样才能使保存在 hash 表中的数据均匀分布。理想状态不太可能实现,由于存储的数据数量远远大于 hash 表存储单元的数量,所以再好的 hash 函数也可能使不同的数据得到相同的映射位置,这就造成了冲突。但是好的 hash 函数可以将这种冲突降到最低。


3.分离链接


解决这种冲突的第一种方法是借助链表来实现,就是将数据实际存放在与 hash 表存储单元相链接的链表中,而不是 hash 的存储单元中。

在这里插入图片描述


4.开放地址


使用开放地址方法解决冲突的时候,数据仍然保存在 hash 表的存储单元中,但是当冲突发生的时候,要再次计算新的地址。

常用的开放地址法是线性探查,就是当对一个数据进行插入删除或者查找的时候,通过 hash 函数计算,发现这个位置不是要找的数据,这时候就检查下一个存储单元,一直找到要操作的数据为止。

除了线性探查外还有二次探查,再 hash 等等方法,都是当一次计算得到的位置不是要找到的数据的时候,怎样再次确定新的位置。


5.完全 hash 表


采用分离链表的方式解决冲突的时候,当多个数据被映射到一个地址的时候,它们就形成了一个链表,要操作这其中的一个数据,那么就必须在这个链表中进行操作。如果 hash 函数选择的好的话,链表会很短,这样的操作近似 O(1) ,但并不是精确的等于 O(1) ,它与链表的长度有关。对于数据的访问的最坏情况的访问也是 O(1) 的 hash 叫做完全 hash 表。

这样的 hash 表是一个两级的 hash 表,第一级的 hash 与使用分离链接方法的 hash 一样,但是 hash 存储单元中指向的不是一个链表,而是另一个 hash 表。

在这里插入图片描述

要小心的选择一级以及二级的 hash 函数可以完全保证在二级 hash 表中不会出现冲突。



版权声明:本文为weixin_41311528原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。