摘要:不同的对象,建议有不同的。所以最好是让均匀的落在不同的。正确写法如下要对两个字段做联合去重时皮一下做,做,放入结果可能为这种情况就是违反了第条,放入了不同的。
我的第一篇文章
以前都在公司的文档里写,后来想想,还是自己找个地方记录下来吧。
今天有个朋友问我hashcode的问题,记录下来,并稍微读下书寻求一点理论知识。
问题如下
有一个属性都是字符串的对象,想放入hashset中,要求,对某一个属性,相同就能放入,不同就不能放入。
朋友的问题是,知道equals咋写,但是不知道hashcode咋写,没有思路。
我的理解是:hashcode是一种比equals粗粒度的比较,打个比方,两个三位数,可以拿十位+个位数作为hashcode,百位+十位+个位才是真正的equals。也就是说,先比较十位+个位数hashcode,如果hashcode不一致,那么这两个对象必然不一致,就不用继续对比了,如果十位+个位数一致,那么他们有可能是一致的,这时继续对比equals,才能知道是否两个对象真的一致。当然这只是一种粗浅的理解,真正的理解还得看((h = key.hashCode()) ^ (h >>> 16)) & (n-1),直观上可以理解为就是hash值的补码取后几位,问题也不大。
public class InnerClass { String a; String b; String c; @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } InnerClass that = (InnerClass)o; return this.a.equals(that.a); }
如果只是寻求一个解,而不是寻求较优的解的话,那么很简单。不是要比equals粗粒度的比较吗?随便来一个比equals粗一丁点的就可以了。如下
@Override public int hashCode() { return Objects.hash(a); }
这个写法得到的结果是对的,但是我总觉得不好,因为在我的理解里,hashcode也是一种效率上的考虑。这样的话,和直接比较this.a又有多大区别呢?
如果强行要粗粒度,我想了一种方法,例如:
@Override public int hashCode() { return Objects.hash(a)/3; }
这样其实也增加不了效率,因为真正要比较的可以理解为hashcode的后几位,除不除以3,影响不大。
假设问题变成:对a和b相同的不放入set,那么hashcode用Object.hash(a)才是一种相对不错的方式。既满足了要求,又有一定的效率上的提升。
这么闭门造车不是个办法,拜读一下《effective java》2e.
整理如下:
1.对同一个对象调用hashcode时,必须返回同样的值。
2.equals相同的对象,必须有相同的hashcode。
3.equals不同的对象,建议有不同的hashcode。
4.当不重写hashcode时,map.put(new A("a"), "b")之后,map.get(new A("a"))不一定能取到"b",因为没重写hashcode,put和get时的两个对象,都是用的Object的hashcode,因为两次new是两个不同的对象,所以hashcode不相同,落在不同的bucket。(即便恰好落在相同的bucket,也不一定能获取到值)
5.hashcode不要返回一个固定值。返回固定值会导致,所有值都落在同一个bucket,这样程序的时间复杂度会增加。所以最好是让hashcode均匀的落在不同的bucket。
6.hashcode均匀落在bucket的一个良好实践是:
1.一个初始值 2.对对象的每一个字段,计算一个值(boolean:f?1:0/byte、char、short、int (int)f/long int(f^(f>>>32))/double Double.doubleToLongBits(f),然后按long处理/对象,对对象的每个字段调用hashcode 3.result = 31*result + c;
7.对不包含在equals里对比的字段,在hashcode中排除掉。
8.对有意义的字段(即equals里对比的字段),不要在hashcode中排除。例如要对a、b做equals,只对a做hashcode,那么可能hashcode在bucket的分布就不均匀了,性能就会下降。
根据这些规则,发现以前的很多想法都是错误的。正确写法如下:
@Override public int hashCode() { int result = 17; result = 31 * result + a.hashCode(); return result; }
要对a、b两个字段做联合去重时:
@Override public int hashCode() { int result = 17; result = 31 * result + a.hashCode(); result = 31 * result + b.hashCode(); return result; }
皮一下:
a做equals,a、b做hashcode,放入hashset结果可能为
b a e
a b c
b c e
这种情况就是违反了第2条,放入了不同的bucket。
之前那个优化的想法不对(ab做equals,a做hash),因为b可能会导致hashcode在bucket的分布不均匀。
补充:bucket是什么? bucket就是hashmap中包含一系列entry的东西,每个bucket是一个链表或者一颗树。
TODO: a.hashcode() object.hash(a) object.hashcode(a) 的区别
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/73763.html
摘要:如果我们不重写的方法,那么就会默认调用的方法小王小王我们可以看到以上的运行结果违背了的规定如果返回,那么方法必须返回相同的整数所以我们需要对对象的方法进行重写通过重写让其与对象的属性关联起来,那么就能够达到为,那么的值也相等。 面试官让你说说==和equals()的区别,重写equals必须重写hashcode方法吗 本身特质来说 ==:操作符 equals():方法 适用...
阅读原文:不同时重写equals和hashCode又怎样! 可能一问到equals和hashCode相关的问题,就会有人讲他们的自反性,对称性,一致性,传递性等几条约定了,此时我不得不佩服,这么多约定竟然都能记得,但我不知道你是不是真的理解呢。 我不同时重写又能如何呢? showImg(https://segmentfault.com/img/remote/1460000018795219); 我...
摘要:介绍的作用是获取哈希码,也称为散列码它实际上是返回一个整数。所以具有相索引的对象,在该散列码位置处存在多个对象,我们必须依靠的和本身来进行区分。 1.hashCode介绍 hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个散列码的作用是确定该对象在散列表中的索引位置,如果有看我的上一篇文章 什么是散列表,那么这里的散列码就相当于上文中根据首字母查...
摘要:它也是用来判断两个对象是否相等,所以也得分不同的情况来说明。什么是的作用是获取哈希码,也称为散列码它返回的一个整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。它定义在的中,这就意味着中的任何类都包含有函数。 前言 万丈高楼平地起,今天的聊点基础而又经常让人忽视的话题,比如==与equals()区别?为何当我们重写完equals()后也要有必要去重写hashcode()呢? .....
摘要:所以在对象没有重写这个方法时,默认使用此方法,即比较对象的内存地址值。结果为可以发现不管对象的内存地址是否相同并不影响其结果,所以类型比较的是数据值而不是内存地址值。 showImg(https://segmentfault.com/img/bVbqpku?w=800&h=344); 今天朋友突然问到一个问题: 两个对象使用x.equals(y)判断结果为true时,两个对象的hash...
阅读 822·2021-11-15 17:58
阅读 3610·2021-11-12 10:36
阅读 3753·2021-09-22 16:06
阅读 912·2021-09-10 10:50
阅读 1300·2019-08-30 11:19
阅读 3290·2019-08-29 16:26
阅读 905·2019-08-29 10:55
阅读 3317·2019-08-26 13:48