Java中equals和hashcode用法
作者:mikey棒棒棒
equals和hashCode的关系
1.基本规则:
- 如果两个对象根据
equals
方法被认为是相等的,那么它们的hashCode
值必须相等。 - 如果两个对象的
hashCode
值相等,它们不一定通过equals
方法相等。
2.原因:
- 哈希表数据结构(如
HashMap
、HashSet
)使用对象的哈希码(hashCode
)来快速查找对象。 - 当你向
HashSet
中添加一个对象时,集合会先调用该对象的hashCode
方法来找到该对象应放置的“桶”(bucket)。 - 如果在桶中找到了对象的哈希码相同的其他对象,集合会再调用
equals
方法来判断这些对象是否真正相等。
equals 方法
用于比较两个对象的内容是否相同。
默认情况下(如果没有重写),Object
类中的equals
方法是比较两个对象的内存地址(即引用),只有当两个引用指向同一个对象时才返回true
。
哈希表的工作原理
- 哈希码 (
hashCode
) 用于快速定位对象: 哈希表使用哈希码来将对象分配到不同的“桶”中,从而加速查找过程。哈希码相同的对象会被放置在同一个桶中。 - 相等 (
equals
) 方法 用于在桶中比较对象: 当两个对象的哈希码相同(即它们位于同一个桶中),哈希表会使用equals
方法来逐一比较这些对象,以判断它们是否真的是相等的。
为什么 equals 和 hashCode 必须保持一致性?
假设两个对象相等(x.equals(y)
返回 true
),但它们的哈希码不同(x.hashCode() != y.hashCode()
):
- 在这种情况下,如果将其中一个对象添加到
HashSet
中,然后查找另一个对象,哈希表会去不同的桶中查找,这样可能找不到它。 - 这违背了
HashSet
等集合的合同,即它们不允许重复对象(根据equals
方法判断相等)。
如果两个对象的哈希码相同(x.hashCode() == y.hashCode()
),但它们不相等(x.equals(y)
返回 false
):
- 这是可以的,因为哈希表会使用
equals
方法来判断真正的相等性。 - 唯一的影响是性能上的下降,因为桶中的对象数量增加了。
HashSet
HashSet 的基本工作原理
HashSet
是基于 哈希表 实现的集合,它用于存储没有重复值的对象。
它的效率很高,通常 add
、remove
和 contains
操作都可以在常量时间(O(1))内完成。然而,这种高效率依赖于正确实现 hashCode
和 equals
方法。
工作步骤:
- 添加元素到
HashSet
中: 当你调用HashSet
的add
方法时,HashSet
会先调用该对象的hashCode
方法计算出该对象的哈希码。 - 查找存储桶(bucket):
HashSet
使用哈希码来决定将该对象存储在哪个 桶(bucket) 中。每个桶可以包含多个对象(这是处理哈希冲突的方式)。桶是HashSet
中用于存储对象的数组中的某个位置。 - 检查对象的相等性: 如果哈希表中已经有其他对象在相同的桶中,
HashSet
会通过调用equals
方法逐个比较这些对象,看看新对象是否已经存在。如果equals
返回true
,则HashSet
不会再插入这个对象,因为集合中不能有重复的对象。 - 添加到集合: 如果
hashCode
和equals
都确认新对象与集合中的任何对象都不相等,HashSet
就会把这个新对象放入集合中。
hashCode 和 equals 如何协作
1. hashCode
用于快速定位
- 当插入或查找元素时,
HashSet
首先根据对象的hashCode
值找到相应的桶(bucket)。 - 哈希码的作用是将对象分散到不同的桶中,以提高查询效率。
2. equals
用于精确比较
- 因为不同对象可能有相同的
hashCode
值(称为哈希冲突),所以HashSet
在找到对应的桶后,还需要进一步用equals
方法检查桶中是否有真正相等的对象。 - 如果
equals
返回true
,表示这个对象已经存在,不再添加。
哈希冲突
哈希冲突(Hash Collision)是在使用哈希表或类似的数据结构时,两个或多个不同的对象生成了相同的哈希码(hashCode
),导致它们被存储在哈希表的同一个位置(即同一个桶,bucket)。虽然哈希码相同,但这些对象在逻辑上是不相等的(equals
返回 false
)。
为什么会发生哈希冲突?
哈希码的范围是有限的(通常是32位整数),但可能存储的对象组合是无限的。
因此,不同的对象有可能生成相同的哈希值,这是哈希冲突发生的根本原因。
如何处理哈希冲突?
现代哈希表通过多种方式处理哈希冲突,以下是常见的两种方法:
链地址法(Separate Chaining):
- 当多个对象具有相同的哈希值时,它们会存储在同一个“桶”中,而每个桶内部可以通过链表、树等数据结构来存放多个对象。
- 当需要查找对象时,先通过哈希值找到对应的桶,然后再在桶内通过逐个比较
equals
来找到目标对象。
开放地址法(Open Addressing):
- 这种方法不使用链表,而是当哈希冲突发生时,直接寻找哈希表中下一个可用的位置(通过某种探查策略,如线性探查、二次探查等),将对象存储到其他空闲位置。
哈希冲突的影响
- 哈希冲突会导致哈希表的性能下降,因为需要处理冲突的额外操作,如遍历链表或进行线性探查。
- 当冲突过多时,哈希表的操作效率会从理想的 O(1) 降到 O(n),特别是在极端情况下(例如所有对象都被放入同一个桶中)。
如何降低哈希冲突的发生
1.使用良好的哈希码算法:
- 避免简单的哈希计算,如直接返回某个字段的值。
- Java 中常用的做法是结合对象的多个字段进行哈希码的计算,并使用质数乘数(例如
31
)来减少冲突。
2.均匀分布哈希码:
- 确保
hashCode
方法生成的哈希值在可能的范围内尽量均匀分布。 - 不同的对象应该有不同的哈希码,即使它们的某些属性相同。
哈希冲突是不可避免的,因为哈希码的范围有限,而对象的可能组合是无限的。因此,即使是使用良好的哈希算法,也难以避免冲突。
总结
在 Java 中,`equals` 和 `hashCode` 方法密切相关,必须保持一致性:如果两个对象通过 `equals` 方法相等,它们的 `hashCode` 也必须相同。
这对于基于哈希的数据结构(如 `HashMap`、`HashSet`)至关重要,因为这些结构依赖哈希值进行快速查找和存储。
为了减少哈希冲突,`hashCode` 的计算通常结合多个字段,并使用质数(如 31)进行乘法,以保证哈希值的均匀分布和较低的冲突率。设计良好的 `equals` 和 `hashCode` 方法可以确保对象比较的准确性与哈希结构的高效性。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。