上一篇地址:持续总结中!2024年面试必问 100 道 Java基础面试题(三十六)-CSDN博客
七十三、什么是hash冲突?
在计算机科学中,特别是在数据结构和算法领域,哈希冲突(Hash Collision)是指在使用哈希(Hash)函数将数据映射到哈希表(Hash Table)时,不同的输入数据经过哈希函数计算后得到了相同的哈希值(即索引值)。由于哈希表的大小是有限的,而可能的输入数据是无限的,因此哈希冲突在实际应用中是不可避免的。
哈希冲突的原因
-
有限的哈希表大小:哈希表通常由数组实现,其大小是固定的,而数据的输入可以是无限的,因此不同的数据项可能映射到同一个哈希表位置。
-
不均匀的哈希值分布:理想情况下,哈希函数应该将输入数据均匀地分布在哈希表的所有位置。然而,由于输入数据的特定特征或哈希函数的局限性,有时哈希值可能不会均匀分布,导致某些位置冲突较多。
哈希冲突的解决策略
-
链地址法(Chaining):在链地址法中,哈希表的每个位置(或称为"桶")都关联一个链表。当发生冲突时,新的数据项被添加到对应位置的链表中。
-
开放寻址法(Open Addressing):开放寻址法中,当发生冲突时,会根据某种探测策略在哈希表中寻找下一个空闲位置。常见的探测方法包括线性探测、二次探测和双重散列。
-
双重哈希(Double Hashing):使用两个哈希函数来计算哈希值,当第一个哈希函数发生冲突时,使用第二个哈希函数计算下一个可能的位置。
-
哈希表扩容:当哈希表的负载因子(已使用的桶数量与总桶数量的比值)超过某个阈值时,可以通过增加哈希表的大小来减少冲突。
示例代码
以下是使用链地址法解决哈希冲突的简单示例:
java
import java.util.LinkedList;
import java.util.List;
class HashTable {
private List<List<String>> table;
public HashTable(int size) {
table = new LinkedList<>();
for (int i = 0; i < size; i++) {
table.add(new LinkedList<>());
}
}
public void insert(String key, String value) {
int index = hashFunction(key);
table.get(index).add(value);
}
private int hashFunction(String key) {
return key.hashCode() % table.size();
}
// 其他方法...
}
public class HashCollisionExample {
public static void main(String[] args) {
HashTable hashTable = new HashTable(10); // 创建一个大小为10的哈希表
hashTable.insert("apple", "fruit");
hashTable.insert("banana", "fruit");
// "apple" 和 "banana" 可能发生冲突,被存储在同一个链表中
}
}
在这个示例中,我们创建了一个简单的哈希表,它使用链地址法来解决哈希冲突。当插入两个键时,如果它们的哈希值相同,它们将被存储在同一个链表中。
注意事项
- 哈希冲突是不可避免的,但可以通过合理的哈希函数设计和冲突解决策略来最小化。
- 哈希表的性能在很大程度上取决于哈希函数的质量和冲突解决策略的有效性。
- 在设计哈希表时,需要考虑负载因子和哈希表的扩容策略,以保持良好的性能。
总结来说,哈希冲突是哈希表操作中的一个常见问题,它发生在不同数据项映射到同一个哈希表位置时。通过使用不同的冲突解决策略,可以有效地管理冲突,从而维护哈希表的高效性能。
七十四、equals和hashCode的区别和联系?
在Java中,equals()
方法和 hashCode()
方法是对象比较和哈希表使用中非常重要的两个方法。它们之间既有区别也有联系:
equals()
方法
-
目的 :
equals()
方法用于比较两个对象是否相等。它的默认实现在java.lang.Object
中比较的是对象的内存地址,但通常需要根据对象的属性进行重写。 -
重写 :当类的对象用于集合(如
HashSet
、HashMap
)或进行对象比较时,应该重写equals()
方法,以确保比较逻辑符合预期。 -
一致性 :
equals()
方法的实现必须满足以下条件:- 自反性:对于任何非空引用
x
,x.equals(x)
应返回true
。 - 对称性:对于任何非空引用
x
和y
,x.equals(y)
应等于y.equals(x)
。 - 传递性:对于任何非空引用
x
、y
和z
,如果x.equals(y)
返回true
,且y.equals(z)
返回true
,则x.equals(z)
也应返回true
。 - 一致性:对于任何非空引用
x
和y
,如果对象的内容没有被修改,多次调用x.equals(y)
应一致地返回true
或false
。 - 对于任何非空引用
x
,x.equals(null)
应返回false
。
- 自反性:对于任何非空引用
hashCode()
方法
-
目的 :
hashCode()
方法返回一个int
类型的哈希码值,用于哈希表的索引。它根据对象的内部状态生成一个数值,这个数值在对象的生命周期内应保持一致。 -
重写 :当重写了
equals()
方法时,通常也应该重写hashCode()
方法,以维护equals()
和hashCode()
之间的一致性。 -
散列冲突:不同的对象可能产生相同的哈希码,这种现象称为散列冲突。一个好的哈希函数会尽量减少这种冲突。
联系
-
相等性与哈希码 :根据Java的约定,如果两个对象通过
equals()
方法比较是相等的,那么它们的hashCode()
方法也应该返回相同的值。这样可以确保使用hashCode()
方法的集合(如HashMap
和HashSet
)能够正常工作。 -
性能 :
hashCode()
方法常用于哈希表中快速定位对象。如果对象的equals()
方法相等,但hashCode()
方法不相等,那么在哈希表中搜索对象时可能会影响性能。
示例代码
java
import java.util.Objects;
public class MyObject {
private int id;
private String name;
public MyObject(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyObject myObject = (MyObject) o;
return id == myObject.id &&
Objects.equals(name, myObject.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
在这个示例中,MyObject
类重写了 equals()
方法和 hashCode()
方法。equals()
方法比较对象的 id
和 name
属性,而 hashCode()
方法根据这些属性生成哈希码。
注意事项
- 仅仅重写
equals()
方法而忽略hashCode()
方法是不正确的,这可能导致哈希表操作出现问题。 - 哈希码的计算应该基于对象的关键属性,这些属性是用于
equals()
方法比较的属性。 - 在并发环境中,如果对象的属性可能会变化,那么
hashCode()
的值也应该相应地变化,以避免哈希表中的键失效。
总结来说,equals()
方法用于比较对象的相等性,而 hashCode()
方法用于生成哈希码,它们之间有紧密的联系。正确实现这两个方法对于确保对象在集合中的正常使用至关重要。