HashSet能确保集合中的元素不重复。那看看是否能回答出以下问题:
-
HashSet底层是如何解决重复的?
-
HashMap的底层实现原理?
-
==与equals的区别?
-
重写equals方法的同时为什么要重写hashCode()方法?
-
HashMap与HashTable的区别?
首先来回答第一个问题。
1. HashSet底层是如何解决重复的?
1. HashSet的实现
-
HashSet底层结构是通过HashMap来实现的。所有存入HashSet的值实际上是作为HashMap的键值,而HashMap的键值实际上一个固定的Object对象,名为PRESENT。
-
去重原理,HashMap的键是唯一的,因此HashSet依赖HashMap的的机制来保证元素的唯一性。
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
}
2. 重复元素的检测过程
- 计算Hash值 通过hashCode方法计算元素的哈希值,用于定位到HashMap的某个桶
- 定位桶 通过哈希值和数组的长度取模,找到存储该元素的目标桶
- 检查是否重复
- 如果目标桶为空,则直接存入
- 如果目标桶的已有元素
- 逐一比较已有元素的哈希值与当前元素的哈希值
- 如果哈希值不相同,则存入链表或树结构的下一位置
- 如果哈希值相同,进一步调用equals方法比较
- 如果equals返回true,说明重复,不存入
- 如果equals返回false,说明不重复,存入
- 逐一比较已有元素的哈希值与当前元素的哈希值
3. 核心依赖方法
为了保证唯一性,HashSet依赖如下两个方法:
- hashCode()
- 决定元素的Hash值,用于快速定位到某个桶
- 不同的对象尽量具有不同的Hash值,以减少冲突
- equals()
- 用于比较两个元素是否逻辑上相等
- 当Hash值相等时,HashSet会用equals用来确认对象是否重复
注意:如果自定义HashSet的元素,需要重写hashCode()和equals()方法,保证逻辑上的一致性:相等的对象必须具有相同的哈希值。
代码实例:
import java.util.HashSet;
import java.util.Objects;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
// 所有对象都返回相同的哈希值,模拟哈希冲突
return 1;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class HashSetExample {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<>();
set.add(new Person("Alice", 25));
set.add(new Person("Bob", 30));
set.add(new Person("Alice", 25)); // 与第一个对象内容相同
System.out.println("Set contents: " + set);
}
}
2. HashMap的底层实现原理
HashMap底层是使用数组加链表或者红黑树来实现的。数组中的元素称为桶(bucket),每个桶实际上是一个链表。当存在哈希冲突时,会将值存入桶中对应的链表中,当链表长度大于阈值时,会转换成红黑树。
当我们在HashMap中查找元素时,它首先使用键的哈希码来计算出元素所在的桶的位置,然后遍历该桶对应的链表,查找键所对应的值。由于每个桶可能包含多个元素,因此查找的时间复杂度通常为O(1)。
需要注意的是,在添加元素时,如果哈希码相同但键不同,则会在同一桶中创建一个新的链表节点,这被称为哈希冲突。为了减少哈希冲突的数量,HashMap在每个桶上设置了一个阈值,当链表长度超过阈值时,会将链表转换为红黑树,以提高查找效率。
另外,为了保持HashMap的性能,当桶的数量达到一定程度时,HashMap会自动扩容。在扩容过程中,HashMap会重新计算每个元素在新数组中的位置,这个过程是比较耗时的,因此需要在实际使用时留足够的空间以减少扩容的频率。
3. ==与equals的区别?
-
==比较的是两个对象的内存地址是否相同,如果用于比较基本类型,则比较的值是否相等。
-
equals默认也是比较引用地址是否相同,若类重写了equals方法,则比较两个对象的内容是否相等。常见的如String、Integer类已经重写了equals方法