吊打面试官(十四)--Java语言中HashSet类一文全掌握

导读

在Java中,Java中的 HashSet 是一个非常重要的集合类,广泛应用于各种编程场景。以下将详细解析 HashSet 的使用场景、底层原理、容易出错的问题、常见面试题以及相关代码示例。祝大家面试必过,吊打面试官。


HashSet的使用场景

去重操作

HashSet 用于存储不重复的元素,适用于需要去重的场景,如日志去重、数据清洗等。去重操作是 HashSet 最常见的应用之一。由于其基于哈希表的实现, HashSet 能够高效地判断元素是否已存在,从而确保集合中不会有重复元素。

快速查找

HashSet 提供了常数时间复杂度的查找操作,适用于需要快速查找元素的场合。由于 HashSet 内部使用哈希表结构,查找操作的时间复杂度为O(1),这使得它在大量数据中查找元素时非常高效。

缓存管理

HashSet 可以用作缓存的一种实现方式,存储缓存中的唯一键,方便快速检索。在缓存系统中, HashSet 可以有效地管理键值对,确保每个键只存储一次,从而提高缓存命中率。


HashSet的底层原理

基于HashMap实现HashSet 内部使用 HashMap 来存储元素,元素作为键,值使用一个固定的 Object 对象(在Java 8及更高版本中,这个对象是一个名为 PRESENT 的静态常量)。

哈希表结构

HashSet 通过 HashMap 的哈希表结构来存储元素,每个元素的哈希值决定了它在哈希表中的位置。哈希表结构使得 HashSet 在查找、插入和删除操作中具有高效的性能,平均时间复杂度为O(1)。

负载因子和再哈希

当哈希表中的元素数量超过初始容量乘以负载因子时,哈希表会进行扩容,重新分配所有元素。


HashSet使用中容易出现的问题

不保证顺序:

HashSet 不保证元素的顺序,即使你按照某种顺序插入元素,它在内部的存储顺序可能是不同的。如果对元素顺序有要求,应该选择 LinkedHashSet 或其他有序集合。


线程不安全”:

HashSet 不是线程安全的,在多线程环境中使用时,可能需要考虑使用同步手段,如
Collections.synchronizedSet 方法。

在多线程环境下,使用 ConcurrentHashMap 或
Collections.synchronizedSet 是更好的选择。


自定义对象的hashCode和equals

如果自定义的类没有正确地重写 hashCode 和 equals 方法,可能会导致 HashSet 无法正确地存储和比较自定义对象。自定义对象在 HashSet 中存储和查找时,必须确保 hashCode 和 equals 方法的一致性,否则可能导致意外的行为。


HashSet常见的面试题

HashSet与HashMap的区别是什么?

数据结构:

HashSet:基于HashMap实现,只存储键,值被固定为一个常量(通常是 PRESENT 对象)。它实现了Set接口,用于存储不重复的元素集合。HashMap:存储键值对,键是唯一的,值可以重复。它实现了Map接口,用于存储和检索键值对。


使用场景:

HashSet:适用于需要存储唯一元素的场景,如去重、缓存管理等。HashMap:适用于需要通过键来查找值的场景,如存储用户信息、构建索引等。


操作方法:

HashSet 只关注唯一元素, HashMap 可以操作键和值。HashSet是如何保持元素唯一性的? HashSet 通过元素的hashCode 和 equals 方法来判断元素是否重复。当添加新元素时, HashSet 会先计算元素的哈希值,如果哈希一致则通过 equals 方法判断是否已存在。


HashSet是如何保持元素唯一性的?

基于 HashMap 实现:

HashSet 内部使用 HashMap 存储元素,其中元素作为键,值则统一使用一个固定的对象(如 PRESENT )。

哈希值和 equals 方法:

在添加元素时,HashSet 首先计算元素的哈希值,以确定其在哈希表中的存储位置。如果发生哈希冲突(即不同元素映射到同一位置),则会调用 equals 方法进行进一步比较。只有当元素的哈希值和 equals 方法均表明该元素已存在时,HashSet 才会拒绝添加重复元素。

如何在HashSet中自定义对象的比较规则?

要在 HashSet 中自定义对象的比较规则,需要重写对象的 hashCode() 和 equals() 方法。这两个方法在 Object 类中定义,所有Java对象都继承了这两个方法。


默认情况下, equals() 方法比较的是对象的引用,而 hashCode() 方法返回的是对象的内存地址。为了在 HashSet 中正确去重,需要根据对象的内容重写这两个方法。以下是一个示例,展示如何在自定义类中重写 hashCode() 和 equals() 方法:

在这个示例中, Person 类重写了 equals() 和 hashCode() 方法,使得两个 Person 对象在 name 和 age 属性相同时被视为相等。

示例代码:

```java
import java.util.HashSet;
import java.util.Objects;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter 和 Setter 方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

    // 重写 equals() 方法
    @Override
    public boolean equals(Object o) {
        // 如果引用相同,直接返回 true
        if (this == o) return true;
        
        // 如果对象为 null 或者类型不同,返回 false
        if (o == null || getClass() != o.getClass()) return false;
        
        // 类型转换
        Person person = (Person) o;
        
        // 比较 name 和 age 是否相等
        return age == person.age &&
               Objects.equals(name, person.name);
    }

    // 重写 hashCode() 方法
    @Override
    public int hashCode() {
        // 使用 Objects.hash() 方法生成哈希码
        return Objects.hash(name, age);
    }

    // 重写 toString() 方法,方便打印对象信息
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + '}';
    }

    // 测试 HashSet 的去重功能
    public static void main(String[] args) {
        HashSet<Person> people = new HashSet<>();

        people.add(new Person("Alice", 30));
        people.add(new Person("Bob", 25));
        people.add(new Person("Alice", 30)); // 重复对象
        people.add(new Person("Charlie", 35));

        // 打印 HashSet 中的元素
        for (Person p : people) {
            System.out.println(p);
        }
    }
}

输出结果:

Person{name='Alice', age=30}

Person{name='Bob', age=25}

Person{name='Charlie', age=35}


重写 equals() 方法:首先检查两个对象是否引用同一个内存地址( this == o ),如果是,则直接返回 true 。然后检查传入的对象是否为 null 或者是否属于不同的类,如果是,则返回 false 。最后,将传入的对象转换为当前类的类型,并比较关键属性( name 和 age )是否相等。如果都相等,则认为两个对象相等。


重写 hashCode() 方法

使用 Objects.hash() 方法根据对象的属性生成哈希码。这确保了当两个对象通过 equals() 方法判断为相等时,它们的哈希码也相同。

测试 HashSet 的去重功能:

创建一个 HashSet<Person> 并添加多个 Person 对象,其中包括一个重复的对象( "Alice", 30 )。由于正确重写了 equals() 和 hashCode() 方法, HashSet 能够识别并去除重复的对象,最终只保留唯一的对象。


结语

以上内容就是关于HashSet使用中所能想到的相关问题的内容,如有遗漏或错误,欢迎留言指正。



( ° °)

原文链接:,转发请注明来源!