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

261 阅读5分钟

image.png

WeakHashMap 是 Java 中一个使用弱引用存储键的 Map 实现。它允许键是弱引用,这意味着当没有其他强引用引用这些键时,键可以被垃圾回收器回收。这使得 WeakHashMap 适合用于缓存场景,其中键的生命周期不需要超过对键的最后一次引用。由于键是弱引用,WeakHashMap 不能保证键值对的永久存储,因此在访问时可能会发现一些键值对已经被回收。WeakHashMap 是非同步的,适用于单线程环境或需要快速访问的场景。

1、 WeakHashMap

WeakHashMap 是 Java 中的一种哈希表实现,它使用弱引用来存储键。这意味着当键不再被其他强引用引用时,它们可以被垃圾回收器回收,即使WeakHashMap中还包含这些键的引用。这种特性使得WeakHashMap常用于缓存实现,其中键的生命周期不需要超过对键的最后一次引用。

设计思考:
  1. 需求场景
    • 在某些应用中,需要一种映射结构,其中的键是弱引用的,这意味着这些键不会阻止它们所引用的对象被垃圾回收器回收。这在缓存实现或临时映射中非常有用,特别是当希望映射表在不再被外部引用时能够自动释放资源时。
  2. 现有技术局限性
    • 标准的 HashMap 和 TreeMap 使用强引用存储键,这意味着只要键存在于映射中,即使没有其他引用,对象也不会被垃圾回收,这可能导致内存泄漏。
  3. 技术融合
    • WeakHashMap 结合了哈希表的快速查找特性和弱引用机制,允许键被垃圾回收器自动回收,而不需要显式的删除操作。
  4. 设计理念
    • WeakHashMap 旨在提供一个映射,其中的键是弱引用的,这意味着如果一个键不再被其他强引用引用,那么它将被垃圾回收器回收,即使它仍然存在于 WeakHashMap 中。
  5. 实现方式
    • WeakHashMap 内部使用一个数组来存储桶(bucket),每个桶可以包含一个或多个通过链表链接的节点(Entry)。键是弱引用,而值是强引用。当垃圾回收器运行时,它会清除所有没有被强引用的键。
2、 数据结构

image.png

图说明:
  • WeakHashMap:表示 WeakHashMap 类的实例,它使用弱引用来存储键。
  • Hash ArrayWeakHashMap 使用一个哈希数组来存储桶(Buckets)。
  • Bucket 1 & Bucket 2:哈希数组中的每个桶可以包含一个或多个节点(Node),这些节点存储实际的键值对。
  • Node:每个桶包含多个节点,这些节点存储键值对,并且通过链表连接。
  • Weak Reference to Key:节点中存储的键是弱引用,这意味着如果这些键没有被其他强引用引用,它们可以被垃圾回收器回收。
  • Value:节点中存储的值。
  • Next Node:节点中的引用,指向链表中的下一个节点。
  • Null:链表的末尾节点的 Next Node 指向 Null。
3、 执行流程

image.png

图说明:
  • 创建 WeakHashMap 实例:初始化 WeakHashMap 对象。
  • 插入元素(put) :执行将键值对插入到 WeakHashMap 的操作。
  • 查找元素(get) :执行根据键查找值的操作。
  • 删除元素(remove) :执行根据键删除键值对的操作。
  • 计算键的哈希码:计算操作键的哈希码以确定其在哈希表中的位置。
  • 确定桶索引:根据哈希码确定键在哈希表中的桶索引。
  • 检查键是否被回收:检查键是否已被垃圾回收器回收。
  • 插入或更新节点:如果桶中没有找到相同的键(基于引用相等性),则在桶的链表中插入新的节点;如果找到,则更新节点的值。
  • 遍历桶中的链表:在确定的桶中遍历链表以查找键值对。
  • 返回节点值:返回找到节点的值。
  • 删除节点:从桶的链表中删除指定的节点。
  • 清理被回收的键值对:清理所有被垃圾回收器回收的键值对。

4、优点

  1. 自动内存管理
    • 键作为弱引用,当没有强引用引用键时,键可以被垃圾回收器回收,从而自动释放内存。
  2. 减少内存泄漏
    • 有助于减少因键的强引用而导致的内存泄漏问题。
  3. 灵活的缓存实现
    • 适合实现缓存,其中条目不需要永久存储,且当内存空间不足时可以自动清除。

5、缺点

  1. 线程不安全
    • WeakHashMap 本身不是线程安全的,需要外部同步来保证线程安全。
  2. 无法保证键的生命周期
    • 由于键是弱引用,无法保证它们在特定时间内一定存在。
  3. 可能的不一致性
    • 在迭代映射时,由于键可能在迭代过程中被回收,可能会导致不一致的结果。

6、使用场景

  • 缓存实现
    • 适用于实现缓存,其中条目不需要永久存储,且当内存空间不足时可以自动清除。
  • 临时映射
    • 当需要临时存储对象映射,且希望映射表在不再被外部引用时能够自动释放资源时。

7、类设计

image.png

8、应用案例

WeakHashMap 通常用于实现缓存策略,尤其是在需要自动清理不再使用的对象时。这是一个简单的缓存系统,用于存储用户的会话信息:

import java.util.WeakHashMap;
import java.util.Map;

// 用户会话类
class UserSession {
    private String sessionId;
    private String userInfo;

    public UserSession(String sessionId, String userInfo) {
        this.sessionId = sessionId;
        this.userInfo = userInfo;
    }

    // 省略 getter 和 setter 方法
}

// 用户会话管理器
class SessionManager {
    private Map<String, UserSession> sessionCache;

    public SessionManager() {
        // 使用 WeakHashMap 作为缓存,以便在用户会话不再被引用时自动清理
        sessionCache = new WeakHashMap<>();
    }

    // 添加或更新用户会话
    public void addOrUpdateSession(String sessionId, UserSession session) {
        sessionCache.put(sessionId, session);
    }

    // 获取用户会话
    public UserSession getSession(String sessionId) {
        return sessionCache.get(sessionId);
    }

    // 移除用户会话
    public void removeSession(String sessionId) {
        sessionCache.remove(sessionId);
    }
}

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

        // 模拟添加用户会话
        sessionManager.addOrUpdateSession("session1", new UserSession("session1", "User1 Info"));
        sessionManager.addOrUpdateSession("session2", new UserSession("session2", "User2 Info"));

        // 获取并打印用户会话信息
        UserSession session1 = sessionManager.getSession("session1");
        System.out.println("Session 1: " + session1);

        UserSession session2 = sessionManager.getSession("session2");
        System.out.println("Session 2: " + session2);

        // 移除用户会话
        sessionManager.removeSession("session1");
    }
}