hashcode和equals

java中的Map一直是很多程序员广为使用的一种容器,关于collection以及Map之类的关系不再赘述,包括map并非collection的一个应用等等。
只是想记录一下很多人意识到但并未100%弄明白的一件事情,就是HashMap,HashTable,LinkedHashMap,中涉及到hashcode和equals的一些基本常识。

TIJ中对于这段有明确的讲解,但是可能相对专业(其实不然,很基础),所以字一多,很多人都看不下去了。从最基本的问题说起吧,

Map mp = new HashMap();
mp.put("1","A");
mp.put("2","B");
mp.put("3","C");

这是一个最基本的值设定。

Map中的内部类Map.Entry<K,V>机制是我们用key取得value的最根本的一个处理。
Map.Entry的作用来比作一个二维数组吧,注意只是类比
a[0][0]={"1","A"}
a[1][0]={"2","B"}
a[2][0]={"3","C"}
那么,如果我们想通过key来取得value的时候如何去做?
ok,循环,找到你要取的key,进而取得名称。

但是,如果map相当的大,假设我们的key是1到10000顺序put到map中,而我正好要取得key是10000的value,岂不是要循环10000次?当然,这里要纠正一个错误,HashMap不是linkedHashMap,所以你put的顺序和get出来的顺序是不同的。

言归正传,要取得key是已知的,但是位置是未知的。世界杯上贺炜解说巴西对朝鲜的比赛我还记忆犹新,对手强大不会令自己害怕,而人们对于未知事务才会恐惧。ok,一个基本存在每个系统中的map结构是不会成就一个未知的空洞的。所以,在map的机制中,引入了hashcode的概念。

首先,刚才说过了,所有的map的存贮都会依赖于Map.Entry<K,V>机制,所以,给定一个key后,调用map.get(key)方法的时候,一定是要循环Map.Entry,从而从相应的key取得相应的value。
据个例子:
我们的map有10000个项目(假定key是1到10000),那么就有10000个Map.Entry<K,V>组。
如果,我们给定一个key是555,需要得到相应的value,那么我们应该能够迅速到达这个key对应的Map.Entry<K,V>的前后,经过有限的几次循环就找到这个key,并且得到相应的value。补充一点,java中什么数据结构的循环检索速度最快?数组(详见TIJ)。基于以上,hashcode应运而生,hashcode的最基本原理就是:
当我们给map.put(key,value)的时候,通过key调用一个hash算法,从而得到一个数值,这个数值本身就是hashcode,这个hashcode作为一个数组的下标存在,数组中存放的就是我们的Map.Entry<K,V>。所以,当我们再一次调用map.get(key)的时候,会首先得到hashcode,然后迅速找到了数组相应的下标,马上又找到了里面的Map.Entry<K,V>,这样就符合了我们刚才的要求,迅速定位到key所对应的Map.Entry<K,V>去。

大概流程是:
map.put("1","A")

由上面的key="1"的"1"算出来,hashcode = 123(假设)
那么存在一个数组a[]
数组a[123]的位置,存放的就是Map.Entry<"1","A">

当我们调用
map.get("1")时
获得hashcode = 123
数组a[123]得到,马上获得Map.Entry<"1","A"> 从而用key="1"取得到value="A"

以上演示出了最基本的一个map存贮的过程过程。

至此,读者应该有一个简单的认识了吧,不过问题接踵而来。
第一,hashcode是如何计算出来的?
回答:看java相关的资料吧。
第二,计算出来hashcode当做下标?那数组要多大啊?
回答:数组是有界的。
第三,数组是有界的?那重复了怎么办?

好,第三个问题引入到下面的讨论中。
诚然,既然数组是有界的,那么计算hashcode势必会出现重复的情况(这个是依据算法的,一般来说好的算法可以最低限度的减少重复,并不绝对),出现重复有什么可怕?把相应的Map.Entry<key,value>顺序都排到这个下标底下去呗。刚才说了,一个好的算法可以最大限度的减少重复,即使出现了重复也是我们可以接收的范围之内,试想,一个数组下标下面就挂着5个Map.Entry<key,value>的话,最多需要循环5次而已嘛。

如果真的出现了重复,我们需要取得key对应的value,这时候需要循环,循环干什么?当然是一个个的通过Map.Entry<key,value>的getKey方法来得到key,看和我们查找用的key是否一致,如果一致的话,直接调用Map.Entry的getValue方法来获得value。

【看和我们查找用的key是否一致】,这句话很重要,引出了另一个雪藏的方法:equals。
大家都知道,equals是判断两个变量是否一致的一个方法,为什么说是判断两个变量是否一致呢?因为equals比较的是对象的地址,给我们造成假象的string 等等基本类型的equals方法早已被悄悄的覆盖过了,他们实际比较的都是数值,所以很多人也就一直活在假象中了。这里,Map.Entry调用getKey后,马上会调用我们自己的传递过来的key的equals方法,因为上面的例子传递的是一个String类型,那么equals方法是java现成的已被覆盖过的比较值一致性的equals方法,一致的话,getValue,不一致继续向下。

至此,一个map的get set过程基本结束,hashcode和equals方法的职责也基本上被曝光的一览无余。

好的,继续回答问题。

1 我们经常说,两个值的hashcode一样,值一定相等么?
回答:不一定啊,除非你的hash算法是100%唯一的。
如果是100%唯一的,这样连equals都省了。
其实像string int之类,不知道大家有没有做过实验,貌似是每个字符的hashcode都不一样的,是否string就是例外呢,hashcode可以唯一确定这个字符?答案是否定的。2^32= 4294967296。这个数值是java中的整形数值的极限,hashcode是一组整形数,2^32个,而string的字符串那就看使用者的想象力了,肯定是无限的,所以,字符串的hashcode势必会有重复的,只是,看运气了,也许有的人一辈子也碰不上,呵。

2 应该覆盖你的hashcode方法和equals方法
回答:嗯,java的多态成就了很多人的继承梦想,一个能用聚合关系或者用普通组合关系的处理方法都一定要用继承,造成了继承的泛滥——这是题外话。
什么时候应该覆盖你的hashcode方法和equals方法呢?回到概念上来说事。hashcode是用来查找你的Map.Entry<K,V>用的,我们的Map中有set和get方法对吧,我们在set的时候生成hashcode1,在get的时候生成hashcode2,那么我们的hashcode1一定要等于hashcode2的,否则set的时候放进去了,取的时候用hashcode2定位到了完全不一样的另一个数组下标,怎么可能再取得刚才放入的值呢。所以,如果我们的hashcode会存在这种变化的对象,我们一定要覆盖他们的hashcode方法。像刚才我们说的string int之类的就不用覆盖了,因为java已经帮我们覆盖好了,可以保证get和set的时候hashcode不会变化,而且equals也是对值得比较。而如果当我们使用一个对象,
比如:
class Studenst {
   private string name; // 姓名
   private int age; // 年龄
   private int no; // 学号
  
   // get set method 省略。。。
}
 
使用这个Student类作为map的key的时候,我们在实例化一个对象的时候,这个对象的hashcode是根据它的地址算出的,那么当我们又一次实例化这个对象的话,地址变化了,hashcode也就随着变化了。什么?你用的是单例模式?单例模式还会存在不同状态的对象可以往map里面放么。
言归正传,仔细观察上面的这个Student,我们发现其实学号一样的两个对象,我们可以判断为两个对象是同一个人,也可判断其“相等”。如此说来,我们应该针对这个对象的"no"来制定一个hash算法,计算出来的hashcode其实就是"no"的hashcode即可:
在Student类中定义hashcode方法:
    public int hashcode() {
        return this.no.hashcode();
    }

同理,对于Student对象比较equals的时候,其实我们比较的还是"no",那么我们也需要覆盖equals方法,在定位到存在复数个Map.Entry<K,V>的时候,可以循环判断用。

    public int equals(Object o) {
        return this.no == (Student)o.getNo();
    }
此时,Student类可以用来随意的set或者get了。

3 两个“值”equals返回true,那么hashcode肯定一致,如果两个值hashcode一致,equals不一定返回true.
回答:这个就不用解释了吧,上面已经很清楚了。

到现在为止,基本上Map的hashcode,equals等等的概念已经用非常白话的语言描述清楚了,这里没有任何晦涩的概念性的东西,都是用我的话来描述的,所以肯定易于理解,不过如果有概念性的错误或者引述不正确的地方,也希望大家指正,虚心接受。:)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值