HashMap put 方法执行的时候,用的是 equals 方法判断当前 key 是否与表中存在的 key 相同。
我们这里没有重写 equals 方法,因此这里返回了 false。
所以,如果我们 hashCode 和 equals 方法都没有重写,那么就会出现下面示意图的情况:
如果,我们重写了 hashCode,没有重写 equals 方法,那么就会出现下面示意图的情况:
总之一句话:在 HashMap 中,如果用对象做 key,那么一定要重写对象的 hashCode 方法和 equals 方法。否则,不仅不能达到预期的效果,而且有可能导致内存溢出。
比如上面的示例,我们放到循环中去,启动参数我们加上 -Xmx10m,运行结果如下:
因为每一次都是 new 出来的 student 对象,hashCode 都不尽相同,所以会不停的触发扩容的操作,最终在 resize 的方法抛出了 OOM 异常。
奇怪的知识又增加了
写这篇文章的时候我翻了一下《Java 编程思想(第 4 版)》一书。
奇怪的知识又增加了两个。
第一个是在这本书里面,对于 HashMap 里面放对象的示例是这样的:
Groundhog:土拨鼠、旱獭。
Prediction:预言、预测、预告。
考虑一个天气预报系统,将土拨鼠和预报联系起来。
这 TM 是个什么读不懂的神仙需求?
幸好 why 哥学识渊博,闭上眼睛,去我的知识仓库里面搜索了一番。
原来是这么一回事。
在美国的宾西法尼亚州,每年的 2 月 2 日,是土拨鼠日。
根据民间的说法,如果土拨鼠在 2 月 2 号出洞时见到自己的影子,然后这个小东西就会回到洞里继续冬眠,表示春天还要六个星期才会到来。如果见不到影子,它就会出来觅食或者求偶,表示寒冬即将结束。
这就呼应上了,通过判断土拨鼠出洞的时候是否能看到影子,从而判断冬天是否结束。
这样,需求就说的通了。
第二个奇怪的知识是这样的。
关于 HashCode 方法,《Java编程思想(第4版)》里面是这样写的:
我一眼就发现了不对劲的地方:result=37*result+c。
前面我们才说了,基数应该是 31 才对呀?
作者说这个公式是从《Effective Java(第1版)》的书里面拿过来的。
这两本书都是 java 圣经啊,建议大家把梦幻联动打在留言区上。
《Effective Java(第1版)》太久远了,我这里只有第 2 版和第 3 版的实体书。
于是我在网上找了一圈第 1 版的电子书,终于找到了对应描述的地方:
可以看到,书里给出的公式确实是基于 37 去计算的。
翻了一下第三版,一样的地方,给出的公式是这样的:
而且,你去网上搜:String 的 hashCode 的计算方法。
都是在争论为什么是 31 。很少有人提到 37 这个数。
其实,我猜测,在早期的 JDK 版本中 String 的 hashCode 方法应该用的是 37 ,后来改为了 31 。
我想去下载最早的 JDK 版本去验证一下的,但是网上翻了个底朝天,没有找到合适的。
书里面为什么从 37 改到 31 呢?
作者是这样解释的,上面是第 1 版,下面是第 2 版:
用方框框起来的部分想要表达的东西是一模一样的,只是对象从 37 变成了 31 。
而为什么从 37 变成 31 ,作者在第二版里面解释了,也就是我用下划线标注的部分。
31 有个很好的特许,即用位移和减法来代替乘法,可以得到更好的性能:
31*i==(i<<5)-1。现代的虚拟机可以自动完成这种优化。
从 37 变成 31,一个简单的数字变化,就能带来性能的提升。
个中奥秘,很有意思,有兴趣的可以去查阅一下相关资料。
真是神奇的计算机世界。
好了,这次的文章就到这里啦。
才疏学浅,难免会有纰漏,如果你发现了错误的地方,可以在留言区提出来,我对其加以修改。
感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。
我是 why 哥,一个被代码耽误的文学创作者,不是大佬,但是喜欢分享,是一个又暖又有料的四川好男人。
欢迎关注我呀。