浅析hashCode

hashCode是什么?

首先,hashCode在计算机领域指的是一个数据经过hash funcation后得到的一个摘要,而这个摘要可以作为索引应用到hash map中去。接下来我们聊聊hashCodejava中是什么样的。

hashCodeJava.lang.Object定义的一个native方法,默认返回的是一个由对象的内存地址转换而来的一个数值。

在Object的子类中会有一些其他的实现,比如在Java.lang.String中,hashCode返回的是这个字符串每个字符的unicode码类乘跟累加混合运算得出来的一个数值。

又比如在Java.lang.Integer中,hashCode返回的就是这个Integer的值

不管怎么去实现hashCodehashCode都必须遵守以下约定:

  1. 同一个对象返回的hashCode必须保持不变
  2. 两个对象如果通过equals判断是相等的,那么hashCode也必须相等

对于通过equals判断为不相等的对象并没有强制要求hashCode也不相等,但是,为这类不相等的对象产生不同的hashCode可以提升操作哈希表的性能。

就拿HashMap来说,其put方法有这样一个判断:

if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
复制代码

如果,equals判断两个对象不相等,但是这两个对象的hashCode一样,那么上面这个if里所有的判断都会执行一遍,这显然会造成没必要的性能损耗。

Java里为什么要有hashCode这个函数?

java里,hashCode是实现Java.util.HashMaphash类数据结构必不可少的一个组成部分。这类数据结构需要用hashCode来定位数据在hash map中的位置。比如,在HashMap#put中,有这么一段代码:

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
复制代码

PS:上面代码中的hash是对象的hashCode经过特殊处理后的一个int类型的值。这个特殊处理主要是将高位的影响向低位传播,这样就不会导致往一些size比较小的HashMap里插入数据时,无论高位(超过size的部分)怎么变,最终都会定位到同一个位置的问题。总而言之,这个特殊处理是为了降低hash冲突发生的概率。

可以看出,hashCode被用来定位keytab这个数组中的位置。

另外,hashCode由于自身的一些特性,hashCode这个函数通常会将第一次调用计算出来的结果存储下来,这样以后每次调用就会直接返回这个值。所以,其实hashCode这个函数的调用所产生的计算量是很少的。这就牵扯到HashMap#put的一个小细节:

if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
复制代码

上面这段代码用来在发生hash碰撞时判断即将插入的key是否跟hash map中已有的的key是同一个。可以看到,这段代码首先就是比较两个keyhash是否相等。只有hash相等时才会进入后面的比较,首先会比较引用地址是否相等,不等时才最终执行equals()方法进行等价关系判断。所以优化体现在哪呢?

p.hash == hash这段代码是需要hashCode做支撑的,相对后面的equals()来说,它的执行成本更低。这个更低一是因为方法执行执行需要做一些额外的操作,比如栈帧出栈入栈,程序计数器的跳转等。另一方面,像Java.lang.String这个类的equals方法,它是通过字符逐个遍历来进行比对的,这个性能就更低了。

一些有意思的点

Java.lang.String#hashCode的实现中,有下面这么一段代码:

h = 31 * h + val[i];
复制代码

我当时看到这段代码有两个疑问:

  • 为什么这里要乘以一个31呢?
  • 别的数不可以吗?

关于这段代码,在Joshua Bloch的《Effective Java》这本书里有这么一段话(第三章第十一节):

The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, because multiplication by 2 is equivalent to shifting. The advantage of using a prime is less clear, but it is traditional. A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance on some architectures: 31 * i == (i << 5) - i. Modern VMs do this sort of optimization automatically.

这里可以解答第一个问题。首先,选择31是因为它既是素数又是奇数。因为一个数乘以一个奇数在乘法溢出时不会导致信息丢失(java里会变成减法)。又因为31 * i = (i << 5) - i ,位移+减法比乘法有着更好的性能。

关于大佬说的第二点好处,我也不是很理解,因为乘除都可以写成位移加上加减运算,区别在于位移的位数以及加减的次数。所以,我觉得应该是31转换乘位移加减法后,减法只需要执行一次即可。性能点应该在于加减执行的次数。那么,关于上面的疑问2,也就有了答案,只要一个数同时满足素数跟奇数,就行。

总结

  1. hashCodehashMap的实现中必不可少的一部分,同时呢,hashCode也间接参与到了hashMap内部的一些优化操作中。
  2. hashCodeequals具备两个约定跟一个建议,约定决定了与hashCode相关的代码(比如hashMap)的执行结果的正确性,而建议决定了执行的性能
全部评论

相关推荐

07-17 11:50
门头沟学院 Java
投递腾讯等公司8个岗位
点赞 评论 收藏
分享
避坑恶心到我了大家好,今天我想跟大家聊聊我在成都千子成智能科技有限公司(以下简称千子成)的求职经历,希望能给大家一些参考。千子成的母公司是“同创主悦”,主要经营各种产品,比如菜刀、POS机、电话卡等等。听起来是不是有点像地推销售公司?没错,就是那种类型的公司。我当时刚毕业,急需一份临时工作,所以在BOSS上看到了千子成的招聘信息。他们承诺无责底薪5000元,还包住宿,这吸引了我。面试的时候,HR也说了同样的话,感觉挺靠谱的。于是,我满怀期待地等待结果。结果出来后,我通过了面试,第二天就收到了试岗通知。试岗的内容就是地推销售,公司划定一个区域,然后你就得见人就问,问店铺、问路人,一直问到他们有意向为止。如果他们有兴趣,你就得摇同事帮忙推动,促进成交。说说一天的工作安排吧。工作时间是从早上8:30到晚上18:30。早上7点有人叫你起床,收拾后去公司,然后唱歌跳舞(销售公司都这样),7:55早课(类似宣誓),8:05同事间联系销售话术,8:15分享销售技巧,8:30经理训话。9:20左右从公司下市场,公交、地铁、自行车自费。到了市场大概10点左右,开始地推工作。中午吃饭时间大约是12:00,公司附近的路边盖饭面馆店自费AA,吃饭时间大约40分钟左右。吃完饭后继续地推工作,没有所谓的固定中午午休时间。下午6点下班后返回公司,不能直接下班,需要与同事交流话术,经理讲话洗脑。正常情况下9点下班。整个上班的一天中,早上到公司就是站着的,到晚上下班前都是站着。每天步数2万步以上。公司员工没有自己的工位,百来号人挤在一个20平方米的空间里听经理洗脑。白天就在市场上奔波,公司的投入成本几乎只有租金和工资,没有中央空调。早上2小时,晚上加班2小时,纯蒸桑拿。没有任何福利,节假日也没有3倍工资之类的。偶尔会有冲的酸梅汤和西瓜什么的。公司的晋升路径也很有意思:新人—组长—领队—主管—副经理—经理。要求是业绩和团队人数,类似传销模式,把人留下来。新人不能加微信、不能吐槽公司、不能有负面情绪、不能谈恋爱、不能说累。在公司没有任何坐的地方,不能依墙而坐。早上吃早饭在公司外面的安全通道,未到上班时间还会让你吃快些不能磨蹭。总之就是想榨干你。复试的时候,带你的师傅会给你营造一个钱多事少离家近的工作氛围,吹嘘工资有多高、还能吹自己毕业于好大学。然后让你早点来公司、无偿加班、抓住你可能不会走的心思进一步压榨你。总之,大家在找工作的时候一定要擦亮眼睛,避免踩坑!———来自网友
qq乃乃好喝到咩噗茶:不要做没有专业门槛的工作
点赞 评论 收藏
分享
06-04 09:27
门头沟学院 Java
点赞 评论 收藏
分享
07-15 12:15
门头沟学院 Java
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务