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

开发 前端
LinkedHashSet 是 Java 集合框架中的一个成员,它结合了 HashSet 的快速查找特性和 LinkedList 的插入顺序保持功能。

LinkedHashSet 是 Java 中的一个集合类,它继承自 HashSet 并实现了 Set 接口。与 HashSet 一样, LinkedHashSet 不允许重复元素,但它维护了元素插入的顺序,即元素迭代的顺序与它们插入的顺序相同。 LinkedHashSet 在内部使用链表来维护元素的插入顺序,同时使用哈希表来快速定位元素,这使得它在保持快速查找性能的同时,还能够按插入顺序遍历元素。由于其基于哈希表和链表的实现, LinkedHashSet 在进行元素插入和删除操作时具有较高的性能,但在随机访问操作上的性能不如基于动态数组的 ArrayList。 LinkedHashSet 是非线程安全的,适用于需要保持插入顺序的场景,如需要有序去重或有序集合操作。

1、 LinkedHashSet

LinkedHashSet 是 Java 集合框架中的一个成员,它结合了 HashSet 的快速查找特性和 LinkedList 的插入顺序保持功能。以下是 LinkedHashSet 的设计:

设计思考:

  1. 需求场景:

在很多应用场景中,需要快速地插入、删除和查找元素,同时也需要保持元素的插入顺序。

例如,在处理用户会话、缓存实现、任务调度等场景时,保持元素的添加顺序是非常重要的。

  1. 现有技术局限性:

HashSet 提供了常数时间的添加、删除和查找性能,但它不保持元素的插入顺序。

TreeSet 保持了元素的排序顺序,但不是插入顺序,且它的性能不如 HashSet。

ArrayList 和 LinkedList 保持了插入顺序,但它们的查找性能为线性时间复杂度。

  1. 技术融合:

为了结合 HashSet 的快速查找能力和 LinkedList 的插入顺序保持能力, LinkedHashSet 应运而生。

  1. 设计理念:

LinkedHashSet 底层使用 HashMap 来存储元素,保证了快速的查找性能。

同时,它在每个 HashMap 的条目上使用一个双向链表来维护元素的插入顺序。

  1. 实现方式:

LinkedHashSet 继承自 HashSet,但重写了 add、 iterator 等方法,以维护插入顺序。

它在内部维护了与 HashMap 条目关联的双向链表的节点,这些节点链接了具有相同哈希值但插入顺序不同的元素。

2、 数据结构

图片图片

图说明:
  • LinkedHashSet:

表示 LinkedHashSet 类的实例,它继承自 HashSet 并维护元素的插入顺序。

  • HashMap:
  • LinkedHashSet 的实现基于 HashMap,用来存储集合中的元素。

  • 数组 (Buckets) :

  • HashMap 使用一个数组来存储桶(Buckets),桶是用于存储 Entry 对象的容器。

  • 哈希桶:

  • 每个桶内部使用链表来解决哈希冲突。

  • 链表 Entry:

  • 每个桶包含多个 Entry 对象,它们通过链表连接。

  • 红黑树 Entry:

  • 当链表长度超过阈值时,链表可能会被转换成红黑树以提高搜索效率。

  • 链表 节点1 和 链表 节点2:

  • 表示链表中的节点,每个节点存储着集合中的一个元素,并指向前一个和后一个节点,形成双向链表。

  • 元素:

  • 存储在 LinkedHashSet 中的最终数据。

3、 执行流程

图片图片

图说明:
  • 创建 LinkedHashSet 实例:

初始化 LinkedHashSet 对象。

  • 添加元素:
  • 将元素添加到 LinkedHashSet。

  • 计算元素的hashCode:

  • 调用元素的 hashCode() 方法计算其哈希码。

  • 确定数组索引位置:

  • 根据哈希码和数组长度确定数组索引位置。

  • 找到对应的哈希桶:

  • 定位到数组中对应的哈希桶。

  • 检查哈希桶中的链表/红黑树:

  • 检查哈希桶中是否已有链表或红黑树结构。

  • 处理哈希冲突:

  • 如果桶中已有元素,处理哈希冲突。

  • 元素添加至链表/红黑树:

  • 将新元素添加至对应索引的链表或红黑树中。

  • 删除元素:

  • 从 LinkedHashSet 删除元素。

  • 重新计算元素的hashCode:

  • 调用元素的 hashCode() 方法计算其哈希码。

  • 确定删除元素的数组索引位置:

  • 根据哈希码和数组长度确定数组索引位置。

  • 找到删除元素的哈希桶:

  • 定位到数组中对应的哈希桶。

  • 从链表/红黑树中删除元素:

  • 从对应索引的链表或红黑树中删除元素。

  • 遍历 LinkedHashSet:

  • 遍历 LinkedHashSet 中的所有元素。

  • 获取数组:

  • 获取 LinkedHashSet 内部的数组。

  • 遍历每个桶:

  • 遍历数组的每个桶。

  • 遍历链表/红黑树:

  • 遍历桶内的链表或红黑树中的所有元素。

  • 读取元素:

  • 读取链表或红黑树中的元素。

4、优点:

  1. 快速查找:

继承自 HashSet,具有快速的查找、添加和删除操作。

  1. 保持插入顺序:

通过内部维护的双向链表,保持了元素的插入顺序。

  1. 空间和时间效率:

  • 相对于 TreeSet, LinkedHashSet 在大多数情况下具有更好的性能。

5、缺点:

  • 内存占用:

相比于 HashSet, LinkedHashSet 需要额外的内存来维护双向链表。

  • 复杂性:

相比于简单的 HashSet, LinkedHashSet 的实现和使用复杂度稍高。

6、使用场景:

  • 需要快速查找和保持插入顺序的场景,如 LRU 缓存、任务调度、用户会话管理等。

7、类设计

图片图片

8、应用案例

LinkedHashSet 通常用于需要保持元素插入顺序的场景。这是一个用户会话管理器,用于跟踪用户的登录状态和最后活跃时间:

import java.util.LinkedHashSet;
import java.util.Set;

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

    public User(String id, String username, long lastActiveTime) {
        this.id = id;
        this.username = username;
        this.lastActiveTime = lastActiveTime;
    }

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

// 用户会话管理器类
class UserSessionManager {
    private Set<User> activeUsers;

    public UserSessionManager() {
        activeUsers = new LinkedHashSet<>();
    }

    // 添加或更新用户会话
    public void addUser(User user) {
        activeUsers.add(user);
    }

    // 获取所有活跃用户
    public Set<User> getActiveUsers() {
        return activeUsers;
    }

    // 移除用户会话
    public void removeUser(String userId) {
        // 遍历 LinkedHashSet 以找到并移除指定用户
        for (User user : activeUsers) {
            if (user.getId().equals(userId)) {
                activeUsers.remove(user);
                break;
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        UserSessionManager sessionManager = new UserSessionManager();

        // 模拟用户登录
        sessionManager.addUser(new User("1", "Alice", System.currentTimeMillis()));
        sessionManager.addUser(new User("2", "Bob", System.currentTimeMillis()));

        // 获取并打印所有活跃用户
        Set<User> activeUsers = sessionManager.getActiveUsers();
        for (User user : activeUsers) {
            System.out.println("Active User: " + user);
        }

        // 模拟用户注销
        sessionManager.removeUser("1");

        // 再次获取并打印所有活跃用户
        activeUsers = sessionManager.getActiveUsers();
        for (User user : activeUsers) {
            System.out.println("Active User: " + user);
        }
    }
}

责任编辑:武晓燕 来源: Solomon肖哥弹架构
相关推荐

2023-03-21 08:41:09

结构设计数据库高性能

2011-05-19 15:25:20

数据库结构

2010-03-25 15:14:36

机房综合布线

2009-07-28 09:42:22

.NET数据访问层

2023-05-31 08:19:00

体系结构设计

2010-05-06 14:30:29

流媒体服务器负载均衡

2009-03-09 13:28:36

结构设计定义.NET

2023-09-15 10:33:41

算法数据结构

2010-05-26 14:00:46

Mobile IPv6

2018-11-27 16:21:36

操作系统Fuchsia谷歌

2022-06-20 09:17:02

数据查询请求数据库

2022-06-15 15:18:50

深度学习图像分割

2020-10-21 14:57:04

数据结构算法图形

2024-10-11 16:43:05

高并发数据结构技巧

2020-05-29 09:41:26

微服务数据工具

2023-10-27 07:04:20

2023-01-09 08:42:04

String数据类型

2017-07-07 08:54:31

Node.js剪贴板管理器开源网络

2024-11-04 06:00:00

redis双向链表

2023-03-02 08:15:13

点赞
收藏

51CTO技术栈公众号