图解HashSet数据结构设计与应用案例

142 阅读5分钟

image.png

HashSet 是 Java 中一个基于哈希表实现的集合,它继承自 AbstractSet 并实现了 Set 接口。HashSet 不保证集合中的元素有序,并且不接受重复元素。由于它是基于哈希表的,所以它支持快速的插入、删除和查找操作,具有较高的性能。HashSet 是非线程安全的,这意味着在单线程环境中它提供了较好的性能,但在多线程环境中需要额外的同步措施来保证线程安全。在现代 Java 应用中,HashSet 常用于需要快速查找和更新的场景,如去重、集合运算等。

1、 HashSet

HashSet 是 Java 集合框架中的一个基本成员,它是 java.util 包下的一个非常常用的集合类。以下是 HashSet 的设计:

设计思考:

  1. 需求场景
    • 在很多应用场景中,需要存储不重复的元素,并且需要快速地添加、删除和查找元素。
    • 例如,在处理配置选项、用户权限、邮件地址列表等场景时,确保元素的唯一性是非常重要的。
  2. 现有技术局限性
    • ArrayList 和 LinkedList 虽然可以存储元素,但它们需要线性时间来查找元素,且不保证元素的唯一性。
  3. 技术融合
    • HashSet 基于 HashMap 实现,它结合了哈希表的快速查找特性来提供常数时间复杂度的添加、删除和查找操作,同时保证了元素的唯一性。
  4. 设计理念
    • HashSet 提供了一个不允许重复元素的数据结构,它使用哈希表的键来存储元素,而不关心值。
    • 这种设计使得 HashSet 在保证元素唯一性的同时,提供了高效的操作性能。
  5. 实现方式
    • HashSet 的每个元素都作为 HashMap 的一个键存储,而对应的值是一个固定的对象(通常是一个名为 PRESENT 的私有静态对象)。

2、 数据结构

image.png

图说明:
  • HashSet:
    • 表示 HashSet 类的实例,用于存储不重复的元素。
  • HashMap:
    • HashSet 的内部实现基于 HashMap
  • 数组 (Buckets) :
    • HashMap 使用一个数组来存储桶(Buckets),桶是用于存储 Entry 对象的容器。
  • 索引1, 索引2, 索引3:
    • 表示数组中的具体索引位置,每个索引对应一个桶。
  • Entry (链表/红黑树) :
    • 每个桶可以包含多个 Entry 对象,它们通过链表或红黑树形式连接。
  • 链表 Entry:
    • 在哈希冲突较少的情况下,Entry 对象通过链表连接。
  • 红黑树 Entry:
    • 当链表长度超过阈值时,链表可能会被转换成红黑树以提高搜索效率。

3、 执行流程

image.png

图说明:
  • 创建 HashSet 实例:
    • 初始化 HashSet 对象。
  • 添加元素:
    • 将元素添加到 HashSet
  • 计算元素的hashCode:
    • 调用元素的 hashCode() 方法计算其哈希码。
  • 确定数组索引位置:
    • 根据哈希码和数组长度确定数组索引位置。
  • 处理哈希冲突:
    • 如果索引位置已有元素,处理哈希冲突。
  • 元素添加至链表/红黑树:
    • 将新元素添加至对应索引的链表或红黑树中。
  • 删除元素:
    • 从 HashSet 删除元素。
  • 计算元素的hashCode:
    • 调用元素的 hashCode() 方法计算其哈希码。
  • 确定数组索引位置:
    • 根据哈希码和数组长度确定数组索引位置。
  • 找到对应的哈希桶:
    • 定位到数组中对应的哈希桶。
  • 从链表/红黑树中删除元素:
    • 从对应索引的链表或红黑树中删除元素。
  • 遍历 HashSet:
    • 遍历 HashSet 中的所有元素。
  • 获取数组:
    • 获取 HashSet 内部的数组。
  • 遍历每个桶:
    • 遍历数组的每个桶。
  • 遍历链表/红黑树:
    • 遍历桶内的链表或红黑树中的所有元素。

4、优点:

  1. 快速操作
    • 提供了常数时间复杂度的添加、删除和查找操作。
  2. 元素唯一性
    • 自动处理元素的唯一性,避免了重复元素的问题。
  3. 实现简单
    • 基于 HashMap 的实现使得 HashSet 的实现非常简洁。

5、缺点:

  1. 不保证顺序
    • HashSet 不保证元素的任何特定顺序,特别是它不保证该顺序恒久不变。
  2. 内存开销
    • 相比于 ArrayListHashSet 可能需要更多的内存来存储哈希表结构。

6、使用场景:

  • 需要存储不重复元素的场景,如缓存、配置选项、权限列表等。

7、类设计

image.png

8、应用案例

HashSet 通常用于去重和快速查找。这是一个用户信息管理系统,用于存储用户的唯一标识符:

import java.util.HashSet;
import java.util.Set;

// 用户类,用于表示系统中的用户
class User {
    private String id;
    private String name;

    public User(String id, String name) {
        this.id = id;
        this.name = name;
    }

    // 省略 getter 和 setter 方法
    @Override
    public String toString() {
        return "User{" +
               "id='" + id + ''' +
               ", name='" + name + ''' +
               '}';
    }
}

// 用户信息管理系统类
class UserInfoSystem {
    private Set<String> userIds;

    public UserInfoSystem() {
        userIds = new HashSet<>();
    }

    // 添加用户到系统
    public void addUser(User user) {
        userIds.add(user.getId());
    }

    // 检查用户是否已存在
    public boolean isUserExist(String userId) {
        return userIds.contains(userId);
    }

    // 获取所有用户
    public Set<String> getAllUsers() {
        return userIds;
    }

    public static void main(String[] args) {
        UserInfoSystem userInfoSystem = new UserInfoSystem();

        // 添加用户到系统
        userInfoSystem.addUser(new User("1", "Alice"));
        userInfoSystem.addUser(new User("2", "Bob"));
        userInfoSystem.addUser(new User("3", "Charlie"));

        // 检查用户是否存在
        boolean userExists = userInfoSystem.isUserExist("2");
        System.out.println("User exists: " + userExists);

        // 获取并打印所有用户
        Set<String> users = userInfoSystem.getAllUsers();
        System.out.println("All users: " + users);
    }
}