想要搞清原理,首先搞清楚名称。Hash的名词解释是 剁碎的食物,杂乱无章的一大堆,动词解释是 切碎,搞砸。Hash函数的作用就是将无序的数据有序组织,也就是熵减操作。 在java语言中Hash函数的作用就是将任意对象转化成一定长度的数字,并将对象参考该数字在内存中存放对象。这样可以方便存放和获取数据。
Hash原理
参考这个例子:
根据数字特征,我们可以选择
H
a
s
h
(
k
e
y
)
=
k
e
y
%
11
Hash(key)=key\%11
Hash(key)=key%11。
于是我们可以将Hash函数结果作为内存地址存放数据。但很明显数字56和6存在地址冲突,如何解决?
直截了当,重新选怎Hash函数。可以
H
a
s
h
(
k
e
y
)
=
k
e
y
%
100
Hash(key)=key\%100
Hash(key)=key%100,这样所有的数据都不存在冲突,但数据在内存中存放过于稀疏,造成空间浪费。所以我们可以通过不断尝试选择出兼顾查找效率和空间利用率的Hash函数。但不管怎么选择,随着数据的增多总存在碰撞的可能,此时我们可以选择合适的方法来避免碰撞。
探测法
1. 线性探测法
线性探测法就是通过线性变化求Hash值。其数学表达式是
H
a
s
h
(
k
e
y
)
=
(
H
a
s
h
1
(
k
e
y
)
+
i
)
%
11
,
i
=
0
,
1
,
2
,
3
…
…
Hash(key)=(Hash_{1}(key)+i)\%11,i=0,1,2,3……
Hash(key)=(Hash1(key)+i)%11,i=0,1,2,3……。
如例子中最后一个数据6,其Hash值和56的Hash值发生冲突,那就将Hash+1,此时与倒数第二个数据7发生冲突,那么Hash+1,直到不冲突为止。这种方法的缺点很明显,随着键值对增多,哈希表中会形成连续键值对,这样会导致后续添加的元素会不断探测至区间末尾,使探测时间边长,这现象成为“一次聚集”。
2. 平方探测法
平方探测法是对线性探测法的改进,主要为了避免“一次聚集”。平方探测每次跳跃
i
2
i^{2}
i2步,这样元素就不会聚集在一起,数学表达式是
H
a
s
h
(
k
e
y
)
=
(
H
a
s
h
1
(
k
e
y
)
+
i
2
)
%
11
,
i
=
0
,
1
,
2
,
3
…
…
Hash(key)=(Hash_{1}(key)+i^{2})\%11,i=0,1,2,3……
Hash(key)=(Hash1(key)+i2)%11,i=0,1,2,3……。
平方探测法完美避免了“一次聚集”,但Hash值相同的元素探测路径一致,并且越靠后的元素探测时间越长,这种现象就是“二次聚集”。
3. 双散列探测法
双散列探测法是为解决“二次聚集”而诞生,设计思路是用魔法打败魔法,此话怎讲? Hash函数本身就具有避免碰撞的性质,我们可以将这种效果叠加起来,其数学表达式是
H
a
s
h
(
k
e
y
)
=
(
H
a
s
h
1
(
k
e
y
)
+
H
a
s
h
2
(
k
e
y
)
∗
i
)
%
11
,
i
=
0
,
1
,
2
,
3
,
…
…
Hash(key)=(Hash_{1}(key)+Hash_{2}(key)*i)\%11,i=0,1,2,3,……
Hash(key)=(Hash1(key)+Hash2(key)∗i)%11,i=0,1,2,3,……。 首次计算Hash值时,i=0,如果发生冲突,就按照1、2、3、4、……顺序尝试, 由于Hash1和Hash2函数本身发生冲突的概率就很低,叠加的结果发生冲突的概率更低。(要注意
H
a
s
h
2
(
k
e
y
)
Hash_{2}(key)
Hash2(key)不能为零,否则在进行探测的时候将会陷入循环)
链表法
在探测法中,遇到数据碰撞就重新生成Hash值,在一维空间上来回跳跃直到寻出合适的存放位置。链表法直接了当,将发生冲突的数据使用链表的方法添加在后面。
Hash在Java中的应用
作用1:
在java中hashCode方法是Object类的native方法,返回值是int, 根据一定的规则将对象和内存物理地址对应,这样便于程序执行过程中快速定位对象。
作用2:
根据Hash函数的原理,可以得到以下性质:
- 两个对象相等,hashCode值一定相等。
- hashCode值不相等,对象一定不同。
- 两个不相等的对象,hashCode值可能相等,也可能不相等。
根据这些属性可以用于辅助判断两个对象是否相等,这也是Java中要求重写equals方法时必须重写hashCode方法。下面是重写equals方法之后,比较User(zhangsan,10,nan)、User2(zhangsan,10,nan)这两个对象的执行流程。
若未重写HashCode方法:
若重写HashCode方法:
比较这两个流程图,很容易发现若重写HashCode方法就可以提前很具HashCode值过滤掉不相等的对象。当对象数目较多时效果更为明显。
文章总结:
hash函数是对无序对象有序组织的手段,方便使用者组织和查找数据。java语言中也利用了这一性质,所有对象都具有一个hashCode,并将hashCode作为物理地址存储对象和查找对象,提高访问效率。