1. hashCode()方法定义
java 中 Object 类的位数不多的方法中有一个 hashCode 方法:
public native int hashCode();
根据源码中方法的定义和注释,我们简单总结下:
- 该方法返回 int 类型数据,并且是本地方法。(源于本地方法请参考我转载的文章:https://www.jianshu.com/p/17a0ae232687)
- 在一次 java 应用执行中,对于同一个对象,hashCode 方法必须返回相同的整数,前提是通过 equals 方法比较认为此对象没有被修改。并且同一应用的不同执行时,hashCode 值不必保持一致。
- 如果两个对象根据 equals 方法相等,那么这俩对象调用 hashCode 方法返回的证书结果也想同。
- 如果两个对象调用 equals 方法不相等,但是调用 hashCode 方法不一定会产生两个不同的结果。
但是最为我们程序猿来说应该意识到对于不同对象产生不同的 hashCode 是会提高哈希表的性能的。
2. hashCode()方法的作用
在 java 中作为所有类的父类的 Object 类能够有 hashCode()这个方法,就说明了此方法有着很大的作用。那我们来看一下 hashCode()方法的作用吧。
- hashCode 的主要作用是为了配合基于散列的集合一起正常运行,如 HashSet, HashMap, HashTable
大家都知道 set 的值不能重复,map 的 key 不能重复。那么如何判断在集合中已经存在呢?
大多数人可能会想到用 equals 比较,这个方法确实可行,但是逐个比较的话成本太高。如果集合中数据很多的话,效率必然会成为瓶颈。此时 hashCode 方法的出现就完美的解决了这个问题。
当集合要添加新的对象时,先调用这个对象的 hashCode 方法,得到对应的 hashcode 值,实际上在 HashMap 的具体实现中会用一个 table 保存已经存进去的对象的 hashcode 值,如果 table 中没有该 hashcode 值,它就可以直接存进去,不用再进行任何比较了;如果存在该 hashcode 值, 就调用它的 equals 方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用 equals 方法的次数就大大降低了,说通俗一点:Java 中的 hashCode 方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。下面这段代码是 java.util.HashMap 的中 put 方法的具体实现:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
put 方法是用来向 HashMap 中添加新的元素,从 put 方法的具体实现可知,会先调用 hashCode 方法得到该元素的 hashCode 值,然后查看 table 中是否存在该 hashCode 值,如果存在则调用 equals 方法重新确定是否存在该元素,如果存在,则更新 value 值,否则将新的元素添加到 HashMap 中。从这里可以看出,hashCode 方法的存在是为了减少 equals 方法的调用次数,从而提高程序效率。
- 关于 hash 表的结构可以参考下这篇文字:http://www.cnblogs.com/dolphin0520/archive/2012/09/28/2700000.html
3. 在重写 equals 方法的同时,必须重写 hashCode 方法
具体原因笔者这会儿有点懒不赘述啦, 另外有两篇写得比较好的文章推荐一下:
参考文章 1:http://bbs.itheima.com/forum.php?mod=viewthread&tid=234627&page=1
参考文章 2:https://www.cnblogs.com/dolphin0520/p/3681042.html#top
4. Hash 函数的设计
Hash 函数设计的好坏直接影响到对 Hash 表的操作效率。下面举例说明:
假如对上述的联系人信息进行存储时,采用的 Hash 函数为:姓名的每个字的拼音开头大写字母的 ASCII 码之和。
因此 address(张三)=ASCII(Z)+ASCII(S)=90+83=173;
address(李四)=ASCII(L)+ASCII(S)=76+83=159;
address(王五)=ASCII(W)+ASCII(W)=87+87=174;
address(张帅)=ASCII(Z)+ASCII(S)=90+83=173;
假如只有这 4 个联系人信息需要进行存储,这个 Hash 函数设计的很糟糕。首先,它浪费了大量的存储空间,假如采用 char 型数组存储联系人信息的话,则至少需要开辟 174*12 字节的空间,空间利用率只有 4/174,不到 5%;另外,根据 Hash 函数计算结果之后,address(张三)和 address(李四)具有相同的地址,这种现象称作冲突,对于 174 个存储空间中只需要存储 4 条记录就发生了冲突,这样的 Hash 函数设计是很不合理的。所以在构造 Hash 函数时应尽量考虑关键字的分布特点来设计函数使得 Hash 地址随机均匀地分布在整个地址空间当中。通常有以下几种构造 Hash 函数的方法:
1)直接定址法
取关键字或者关键字的某个线性函数为 Hash 地址,即 address(key)=a*key+b;如知道学生的学号从 2000 开始,最大为 4000,则可以将 address(key)=key-2000 作为 Hash 地址。
2)平方取中法
对关键字进行平方运算,然后取结果的中间几位作为 Hash 地址。假如有以下关键字序列{421,423,436},平方之后的结果为{177241,178929,190096},那么可以取{72,89,00}作为 Hash 地址。
3)折叠法
将关键字拆分成几部分,然后将这几部分组合在一起,以特定的方式进行转化形成 Hash 地址。假如知道图书的 ISBN 号为 8903-241-23,可以将 address(key)=89+03+24+12+3 作为 Hash 地址。
4)除留取余法
如果知道 Hash 表的最大长度为 m,可以取不大于 m 的最大质数 p,然后对关键字进行取余运算,address(key)=key%p。
在这里 p 的选取非常关键,p 选择的好的话,能够最大程度地减少冲突,p 一般取不大于 m 的最大质数。
作者 @ 没有故事的老大爷
一个人至少拥有一个梦想,有一个理由去坚强。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于