相等 和 Hash Code

从一般角度来看,Equality 是不错的,但是 hash code 更则具技巧性。如果我们在 hash code上多下点功夫,我们就能了解到 hash code 就是用在细微处去提升性能的。

大部分的数据结构使用equals去检查是否他们包含一个元素。例如:

1
2
List<String> list = Arrays.asList("a", "b", "c");
boolean contains = list.contains("b");

这个变量 contains 是true。因为他们是相等的,虽然b的实例化(instance)虽然不完全一样(再说一次,忽略String interning)。

将传递给 contains 的实例与每个元素进行比较很浪费时间。还好,整个这类数据结构使用了一种更高效的方法。它不会将请求的实例与每个元素比较,而是使用捷径,找到可能与之相等的实例,然后只比较这几项。

这个捷径就是哈希码——从对象计算出来的一个能代表该对象的整数值。与哈希码相同的实例不必相等,但相等的实例一定有相同的哈希码。(或者说应该有,我们稍后会对这个问题进行简单讨论)。这类的数据结构常常使用这种技术命名,在名称中加入 Hash 以便识别,其中最具代表性的就是 HashMap。

一般情况下它们会这样进行:

  • 添加一个元素的时候,使用它的哈希码来计算存放在内部数组(称为桶)中的位置(序号)。
  • 另一个不等同的元素如果具有相同的哈希码,它会被放在同一个桶中,与原来那个放在一起,比如把它们放在一个列表中。
  • 如果传递一个实例给 contains 方法,会先计算它的哈希码来找到桶,只有同一个桶中的元素需要与这个实例进行比较。

使用这种方法实现 contains 的情况很少,在理想的状态下根本不需要 equals 比较。

将 equals、hashCode 定义在 Object 中。

关于哈希的一些思考

如果把 hashCode 作为一种快捷方式取决于其是否相等,那么只有一件事情我们需要关心:相等的对象应该有一致的哈希码。

这也是为什么,如果我们覆写 equals 方法,就必须创建一个匹配的 hashCode 实现!此外,实现 equal 应该是依据我们的实现而实现的,这可能会导致没有相同的哈希码,因为他们使用的是 Object 的实现。

hashCode 约定

从原文档引用:

对于 hashCode 的一般约定:

  • 在 Java 应用程序中,任何时候对同一对象多次调用 hashCode 方法,都必须一直返回同样的整数,对它提供的信息也用于对象的相等比较,且不会被修改。这个整数在两次对同一个应用程序的执行不中不需要保持一致。
  • 如果两个对象通过 equals(Object) 方法来比较相等,那么这两个对象的 hashCode 方法必须产生同样的整型结果。
  • 如果两个对象通过 equals(Object) 方法比较结果不等,这两个对象的 hashCode 不必产生同不整型结果。然而,开发者应该了解对不等的对象产生不同的整型结果有助于提高哈希表的性能。

第一条反映了 equals 的一致性。第二条是我们在上面提到的要求。第三条陈述了我们下面要讨论的一个重要细节。

实现 hashCode

Person.hashCode 有个很简单的实现:

1
2
3
4
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}

通过计算相关字段的哈希码,再把这些哈希码组合起来得到 person 的哈希码。它们用 Object 的工具函数 hash 来参与计算。

选择字段

然而什么字段才是相关的?这些要求有助于回答这个问题:如果相等的对象必须有相同的哈希码,那么在计算哈希码的时候就不应该使用那些不用于相等性检查的字段。(否则,如果两个对象只有那些字段不同的话,它们会相等但哈希码不同。)

所以用于计算哈希码的那些字段应该是用于相等性比较的那些字段的子集。默认情况下,它们会使用相同的字段,但有几个细节需要考虑。

一致性

第一是一致性要求。它应该经过非常严格的计算。如果有字段产生了变化,哈希码也应该允许变化(对于可变类来说,这往往是不可避免的),依赖哈希的数据结构并未准备应付这种情况。

正如我们在上面看到的那样,哈希码用于确定一个元素的桶,但是如果哈希相关的字段发生变化,并不会立即重新计算哈希码,而且内部的数组也不会更新。

这就意味着,再对一个相等的对象甚至同一个对象的查询会失败!这个数据结构会计算当前的哈希码,这个哈希码与实例存入时的哈希码并不相同,这直接导致找错了桶。

小结:最好不要用可变的字段来计算哈希码!

性能

哈希码可能最终会在每次调用 equals 的时候计算,这可能正好发生在代码中性能极为关键的部分,所以考虑性能是很有意义的。相比之下 equals 的优化空间就非常小。

除非是使用了复杂的算法,或者使用的字段非常非常多,组合他们哈希码的计算成本可以忽略不计,因为这不可避免。但是应该考虑是否所有字段都需要包含在计算中!尤其应该以审视的眼光来看待集合,例如计算列表和集合中所有元素的哈希码。需要根据不同的情况来考虑是否需要它们参与计算。

如果性能是关键,使用 Object.hash 就可能不是最好的选择,因为它会为可变参数创建数组。

一般的优化原则是:谨慎处理!使用一个公共哈希算法的,可能需要放弃集合,并在分析可能的改进之后进行优化。

碰撞

如果只关注性能,下面这个实例怎么样?

1
2
3
4
@Override
public int hashCode() {
return 0;
}

毫无疑问,它很快。而且相等的对象会有相同的哈希码,这也让我们觉得不错。还有个亮点,它不涉及可变的字段!

但是,想想我们提到的桶是什么?这种情况下所有实例会被装进同一个桶中!通常这会导致使用一个链表来容纳所有元素,这样的性能太糟糕了——比如,每次执行 contains 都会对列表进行线性扫描。

因此,我们得让每个桶里的内容尽可能的少!一个即使对非常相似的对象计算的哈希码也大不相同的算法,会是一个不错的开始。

如何取得,一定程度上取决于选择的字段。我们用于计算的细节,更多时候是为了生成不同的哈希码。注意,这与我们对性能的想法完全相反。结果很有趣,用太多或者太少字段都会导致性能不佳。

防止碰撞的算法是哈希算法的另一部分。

计算哈希

计算字段的哈希码最简单的办法就是直接调用这个字段的 `hashCode`。可以手工来进行合并。一个公共算法是从任意的某个数开始,让它与另一个数(通常是一个小素数)相乘,再加上一个字段的哈希码,然后重复:

1
2
3
4
5
int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
return result;

这有可能造成溢出,但这不是什么大问题,因为在 Java 中不会引发异常。

注意,如果输入数据有着特定的模式,最好的哈希算法都可能出现异常频繁的碰撞。举个简单的例子,假设我们用一个点的 x 坐标和 y 坐标来计算哈希。一开始不太糟,直到我们发现这样一条直线上的点:f(x) = -x,这些点的 x + y = 0。就会发生大量的碰撞!

转载于:https://www.cnblogs.com/frankyou/p/9848681.html

如何正确实现 Java 中的 HashCode相关推荐

  1. 浅谈Java中的hashcode方法

    哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: 1 public native int hashCode(); 根据 ...

  2. 理解和正确使用Java中的断言(assert)

    理解和正确使用Java中的断言(assert) 一.语法形式:     Java2在1.4中新增了一个关键字:assert.在程序开发过程中使用它创建一个断言(assertion),它的 语法形式有如 ...

  3. Java中的hashcode详解

    哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个 ...

  4. java中的hashcode方法作用以及内存泄漏问题

    本文装载:http://hi.baidu.com/iduany/item/6d66dfc9d5f2da1650505870 hashCode()方法的作用&使用分析 一直以来都想写篇文章来说明 ...

  5. 【Java】java中 ==,equals,hashcode

    ==运算符是判断两个对象是不是同一个对象,即他们的地址是否相等 object类中equals与==是等效的 覆写equals更多的是追求两个对象在逻辑上的相等,你可以说是值相等,也可说是内容相等.(覆 ...

  6. Java中的HashCode(1)之hash算法基本原理

    一.为什么要有Hash算法 Java中的集合有两类,一类是List,一类是Set.List内的元素是有序的,元素可以重复.Set元素无序,但元素不可重复.要想保证元素不重复,两个元素是否重复应该依据什 ...

  7. java关于重写正确,在Java中,以下关于方法重载和方法重写描述正确的是( )

    在Java中,以下关于方法重载和方法重写描述正确的是( ) 答:方法重写的返回值类型必须相同或相容.(或是其子类) 对乙酰氨基酚临床用途是抗炎抗风湿作用 答:错 观念艺术是哲学的反思,传统的理论可以对 ...

  8. 理解Java中的hashCode 和 equals 方法

    2019独角兽企业重金招聘Python工程师标准>>> 在Java里面所有的类都直接或者间接的继承了java.lang.Object类,Object类里面提供了11个方法,如下: 1 ...

  9. Java中的HashCode(2)之Hashset造成的内存泄露

    所谓内存泄露就是一个对象占用的一块内存,当这个对象不在被使用时,该内存还没有被收回. 例子 package cn.xy.test; public class Point2 {  private int ...

最新文章

  1. oracle入门知识实施,新手必须了解的oracle入门知识
  2. python3 print设置不换行
  3. Wireshark基本介绍和学习TCP三次握手
  4. 华为鸿蒙系统手机销量,两个品牌助力华为新生,但最终会是谁拯救谁
  5. TLD(Tracking-Learning-Detection)学习与源码理解之(六)
  6. 网鼎杯2020php反序列化,2020-网鼎杯(青龙组)_Web题目 AreUserialz Writeup
  7. iPhone 11外壳保护套曝光:噢,这个浓厚的老干部风格
  8. DP-代理模式(Proxy Pattern)
  9. mysql 大写数据库名 无法识别_MySQL数据库名、表名大小写问题
  10. 异步分段处理海量数据
  11. 一分钟先生---走出软件作坊:三五个人十来条枪 如何成为开发正规军(三十二)...
  12. 用DIV+Css+Jquery 实现的旧版微信飞机大战。
  13. 个人打卡签到表html代码,考勤每日签到表模板
  14. 银河麒麟系统查看网络设置命令_银河麒麟配置说明
  15. 单片机c语言中断延时,单片机中C语言延时函数
  16. 【半年时光-追寻你的足迹】
  17. CPU32和CPU64区别
  18. 关于PostgreSQL的绑定变量窥视的问题详解
  19. 创龙Xilinx Zynq-7000系列SoC高性能处理器开发板的SFP+接口、FMC接口
  20. 为什么录像都是用.avi而不是.mp4?

热门文章

  1. 浅析STM32之usbh_def.H
  2. 20155305乔磊2016-2017-2《Java程序设计》第四周学习总结
  3. hdu 1023 Train Problem II
  4. prefix.pch文件的一些简单使用
  5. crawler_微信采集方案
  6. 云原生全球最大峰会之一KubeCon首登中国 Kubernetes将如何再演进?
  7. mongodb分布式集群搭建手记
  8. 第9章 使用ssh服务管理远程主机。
  9. Android listview addHeaderView 和 addFooterView 详解
  10. 开源 免费 java CMS - FreeCMS1.9 会员组管理