当前位置:网站首页 > 技术博客 > 正文

hashcode equals



廖雪峰
资深软件开发工程师,业余马拉松选手。

我们知道Map是一种键-值(key-value)映射表,可以通过key快速查找对应的value。

以为例,观察下面的代码:

之所以能根据直接拿到,原因是它内部通过空间换时间的方法,用一个大数组存储所有,并根据key直接计算出应该存储在哪个索引:

如果的值为,计算得到的索引总是,因此返回为,如果的值为,计算得到的索引总是,因此返回为,这样,就不必遍历整个数组,即可直接读取对应的。

当我们使用存取的时候,就会引出一个问题:

我们放入的是字符串,但是,当我们获取的时,传入的变量不一定就是放入的那个对象。

换句话讲,两个应该是内容相同,但不一定是同一个对象。测试代码如下:

因为在的内部,对做比较是通过实现的,这一点和查找元素需要正确覆写是一样的,即正确使用必须保证:作为的对象必须正确覆写方法。

我们经常使用作为,因为已经正确覆写了方法。但如果我们放入的是一个自己写的类,就必须保证正确覆写了方法。

我们再思考一下为什么能通过直接计算出存储的索引。相同的对象(使用判断时返回)必须要计算出相同的索引,否则,相同的每次取出的就不一定对。

通过计算索引的方式就是调用对象的方法,它返回一个整数。正是通过这个方法直接定位对应的的索引,继而直接返回。

因此,正确使用必须保证:

  1. 作为的对象必须正确覆写方法,相等的两个实例调用必须返回;
  2. 作为的对象还必须正确覆写方法,且方法要严格遵循以下规范:
    • 如果两个对象相等,则两个对象的必须相等;
    • 如果两个对象不相等,则两个对象的尽量不要相等。

即对应两个实例和:

  • 如果和相等,那么一定为,则必须等于;
  • 如果和不相等,那么一定为,则和尽量不要相等。

上述第一条规范是正确性,必须保证实现,否则不能正常工作。

而第二条如果尽量满足,则可以保证查询效率,因为不同的对象,如果返回相同的,会造成内部存储冲突,使存取的效率下降。

正确编写的方法我们已经在编写equals方法一节中讲过了,以类为例:

把需要比较的字段找出来:

  • firstName
  • lastName
  • age

然后,引用类型使用比较,基本类型使用比较。

在正确实现的基础上,我们还需要正确实现,即上述3个字段分别相同的实例,返回的必须相同:

注意到类已经正确实现了方法,我们在计算的时,反复使用,这样做的目的是为了尽量把不同的实例的均匀分布到整个范围。

和实现方法遇到的问题类似,如果或为,上述代码工作起来就会抛。为了解决这个问题,我们在计算的时候,经常借助来计算:

所以,编写和遵循的原则是:

用到的用于比较的每一个字段,都必须在中用于计算;中没有使用到的字段,绝不可放在中计算。

另外注意,对于放入的对象,没有任何要求。

既然内部使用了数组,通过计算的直接定位所在的索引,那么第一个问题来了:返回的范围高达±21亿,先不考虑负数,内部使用的数组得有多大?

实际上初始化时默认的数组大小只有16,任何,无论它的有多大,都可以简单地通过:

把索引确定在0 ~ 15,即永远不会超出数组范围,上述算法只是一种最简单的实现。

第二个问题:如果添加超过16个到,数组不够用了怎么办?

添加超过一定数量的时,会在内部自动扩容,每次扩容一倍,即长度为16的数组扩展为长度32,相应地,需要重新确定计算的索引位置。例如,对长度为32的数组计算对应的索引,计算方式要改为:

由于扩容会导致重新分布已有的,所以,频繁扩容对的性能影响很大。如果我们确定要使用一个容量为个的,更好的方式是创建时就指定容量:

虽然指定容量是,但内部的数组长度总是2n,因此,实际数组长度被初始化为比大的(214)。

最后一个问题:如果不同的两个,例如和,它们的恰好是相同的(这种情况是完全可能的,因为不相等的两个实例,只要求尽量不相等),那么,当我们放入:

时,由于计算出的数组索引相同,后面放入的会不会把覆盖了?

当然不会!使用的时候,只要不相同,它们映射的就互不干扰。但是,在内部,确实可能存在不同的,映射到相同的,即相同的数组索引上,肿么办?

我们就假设和这两个最终计算出的索引都是5,那么,在的数组中,实际存储的不是一个实例,而是一个,它包含两个,一个是的映射,一个是的映射:

在查找的时候,例如:

内部通过找到的实际上是,它还需要遍历这个,并找到一个,它的字段是,才能返回对应的实例。

我们把不同的具有相同的的情况称之为哈希冲突。在冲突的时候,一种最简单的解决办法是用存储相同的。显然,如果冲突的概率越大,这个就越长,的方法效率就越低,这就是为什么要尽量满足条件二:

方法编写得越好,工作的效率就越高。

要正确使用,作为的类必须正确覆写和方法;

一个类如果覆写了,就必须覆写,并且覆写规则是:

  • 如果返回,则返回值必须相等;
  • 如果返回,则返回值尽量不要相等。

实现方法可以通过辅助方法实现。

版权声明


相关文章:

  • xml转化为json格式2025-03-09 09:00:59
  • dmi linux2025-03-09 09:00:59
  • jvm调优实战简书2025-03-09 09:00:59
  • 霍夫曼树的构造2025-03-09 09:00:59
  • 迈迪工具集正版多少钱2025-03-09 09:00:59
  • html表单数据如何提交到本页2025-03-09 09:00:59
  • java内部类有哪些2025-03-09 09:00:59
  • 单例模式的8种写法2025-03-09 09:00:59
  • vue渲染页面流程2025-03-09 09:00:59
  • modbus crc16校验源码2025-03-09 09:00:59