MST

星途 面试题库

面试题:Java WeakHashMap在复杂缓存策略下的深度优化

假设在一个复杂的缓存场景中,需要实现多级缓存,且部分缓存数据使用WeakHashMap存储,要满足高效读写、内存回收合理以及动态调整缓存策略的需求。请详细描述你将如何设计和优化这个缓存系统,包括WeakHashMap的使用方式、数据结构设计以及相关算法实现。
34.9万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. 设计思路

  1. 多级缓存架构:采用至少两级缓存,如一级缓存(L1)使用ConcurrentHashMap,因为它支持高并发读写,适合快速响应常用数据请求;二级缓存(L2)使用WeakHashMap,用于存储相对不那么频繁访问的数据,利用其弱引用特性在内存紧张时自动回收。
  2. 缓存策略:制定动态的缓存淘汰策略,如基于时间(Time - To - Live, TTL)和基于访问频率(Least Frequently Used, LFU)相结合的策略。对于L1缓存,优先使用LFU策略淘汰长时间未使用且访问频率低的数据;对于L2缓存,由于WeakHashMap本身的特性,无需额外的淘汰策略,但可结合TTL确保数据的时效性。

2. WeakHashMap的使用方式

  1. 初始化:在初始化二级缓存时,创建WeakHashMap实例,例如:
WeakHashMap<String, Object> weakCache = new WeakHashMap<>();
  1. 存储数据:将数据存入WeakHashMap时,键使用强引用,值使用弱引用。这样当值不再被其他地方强引用时,在内存不足时会被垃圾回收器回收。例如:
weakCache.put(key, new WeakReference<>(value));
  1. 读取数据:从WeakHashMap读取数据时,需要从WeakReference中获取实际的值,并检查值是否已被回收。例如:
WeakReference<Object> ref = weakCache.get(key);
Object value = ref != null? ref.get() : null;

3. 数据结构设计

  1. 缓存数据结构:设计一个通用的缓存项类,例如:
class CacheEntry {
    private Object value;
    private long lastAccessTime;
    private int accessCount;

    public CacheEntry(Object value) {
        this.value = value;
        this.lastAccessTime = System.currentTimeMillis();
        this.accessCount = 1;
    }

    // Getters and setters
    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    public long getLastAccessTime() {
        return lastAccessTime;
    }

    public void updateAccessTime() {
        this.lastAccessTime = System.currentTimeMillis();
        this.accessCount++;
    }

    public int getAccessCount() {
        return accessCount;
    }
}
  1. 一级缓存数据结构ConcurrentHashMap<String, CacheEntry>,使用ConcurrentHashMap存储键值对,其中值为CacheEntry对象,方便记录访问时间和访问频率等信息。
  2. 二级缓存数据结构WeakHashMap<String, WeakReference<CacheEntry>>,如上述WeakHashMap的使用方式,存储二级缓存数据。

4. 相关算法实现

  1. 读取算法
    • 首先从L1缓存中查找数据。如果找到,更新CacheEntry的访问时间和访问频率,然后返回数据。
    • 如果L1缓存未命中,则从L2缓存中查找。找到后,将数据移至L1缓存(如果L1缓存未满),并更新L1缓存中对应CacheEntry的信息,然后返回数据。如果L2缓存也未命中,则从数据源获取数据,存入L1缓存(如果L1缓存未满),并根据需要存入L2缓存。
public Object get(String key) {
    CacheEntry entry = l1Cache.get(key);
    if (entry != null) {
        entry.updateAccessTime();
        return entry.getValue();
    }

    WeakReference<CacheEntry> ref = l2Cache.get(key);
    if (ref != null) {
        CacheEntry l2Entry = ref.get();
        if (l2Entry != null) {
            l2Entry.updateAccessTime();
            l1Cache.put(key, l2Entry);
            return l2Entry.getValue();
        }
    }

    // 从数据源获取数据
    Object data = getDataFromSource(key);
    if (data != null) {
        CacheEntry newEntry = new CacheEntry(data);
        l1Cache.put(key, newEntry);
        if (l1Cache.size() > l1CacheCapacity) {
            removeLeastFrequentlyUsedFromL1();
        }
        l2Cache.put(key, new WeakReference<>(newEntry));
    }
    return data;
}
  1. 写入算法
    • 直接将数据写入L1缓存。如果L1缓存已满,根据LFU策略淘汰一个CacheEntry,并将新数据写入。
    • 同时,将数据写入L2缓存(如果需要)。
public void put(String key, Object value) {
    CacheEntry entry = new CacheEntry(value);
    l1Cache.put(key, entry);
    if (l1Cache.size() > l1CacheCapacity) {
        removeLeastFrequentlyUsedFromL1();
    }
    l2Cache.put(key, new WeakReference<>(entry));
}
  1. LFU淘汰算法:在L1缓存满时,遍历ConcurrentHashMap,找到访问频率最低且最早访问的CacheEntry进行淘汰。
private void removeLeastFrequentlyUsedFromL1() {
    CacheEntry lfuEntry = null;
    for (CacheEntry entry : l1Cache.values()) {
        if (lfuEntry == null || entry.getAccessCount() < lfuEntry.getAccessCount() ||
                (entry.getAccessCount() == lfuEntry.getAccessCount() && entry.getLastAccessTime() < lfuEntry.getLastAccessTime())) {
            lfuEntry = entry;
        }
    }
    if (lfuEntry != null) {
        l1Cache.values().remove(lfuEntry);
        l2Cache.values().removeIf(ref -> ref.get() == lfuEntry);
    }
}
  1. TTL过期检查算法:可以通过一个定时任务(如ScheduledExecutorService)定期检查L1和L2缓存中的数据是否过期(根据CacheEntry中的lastAccessTime和设定的TTL),过期则删除。
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
    long currentTime = System.currentTimeMillis();
    l1Cache.entrySet().removeIf(entry -> currentTime - entry.getValue().getLastAccessTime() > ttl);
    l2Cache.entrySet().removeIf(entry -> {
        WeakReference<CacheEntry> ref = entry.getValue();
        CacheEntry cacheEntry = ref.get();
        return cacheEntry == null || currentTime - cacheEntry.getLastAccessTime() > ttl;
    });
}, 0, 1, TimeUnit.MINUTES);

通过上述设计和算法实现,可以满足复杂缓存场景中高效读写、内存回收合理以及动态调整缓存策略的需求。