Java 设计支持动态调整的LFU缓存: 需包含热度衰减曲线和淘汰策略监控

1. 创建LFU缓存类

java 复制代码
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

/**
 * LFU Cache with dynamic adjustment, decay curve and eviction monitoring
 * 支持动态调整的LFU缓存系统,包含热度衰减曲线和淘汰策略监控
 */
public class LFUCache<K, V> {
    
    // 缓存存储
    private final Map<K, CacheNode<K, V>> cache;
    
    // 频率桶,每个频率对应一个节点链表
    private final Map<Integer, FrequencyBucket<K, V>> frequencyBuckets;
    
    // 最小频率
    private int minFrequency;
    
    // 缓存最大容量
    private int capacity;
    
    // 当前缓存大小
    private int size;
    
    // 热度衰减因子
    private double decayFactor;
    
    // 访问时间窗口(毫秒)
    private long timeWindow;
    
    // 淘汰监听器
    private final List<EvictionListener<K, V>> evictionListeners;
    
    // 统计信息
    private final CacheStatistics statistics;
    
    // 锁
    private final ReentrantLock lock = new ReentrantLock();
    
    /**
     * 构造函数
     * @param capacity 缓存容量
     */
    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new ConcurrentHashMap<>();
        this.frequencyBuckets = new ConcurrentHashMap<>();
        this.minFrequency = 0;
        this.size = 0;
        this.decayFactor = 0.9;
        this.timeWindow = 60000; // 默认1分钟
        this.evictionListeners = new ArrayList<>();
        this.statistics = new CacheStatistics();
    }
    
    /**
     * 获取缓存值
     * @param key 键
     * @return 值
     */
    public V get(K key) {
        lock.lock();
        try {
            CacheNode<K, V> node = cache.get(key);
            if (node == null) {
                statistics.incrementMisses();
                return null;
            }
            
            // 更新统计信息
            statistics.incrementHits();
            
            // 更新访问频率
            updateFrequency(node);
            
            return node.value;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 添加缓存项
     * @param key 键
     * @param value 值
     */
    public void put(K key, V value) {
        put(key, value, -1);
    }
    
    /**
     * 添加缓存项(带过期时间)
     * @param key 键
     * @param value 值
     * @param expireTime 过期时间(毫秒),-1表示永不过期
     */
    public void put(K key, V value, long expireTime) {
        lock.lock();
        try {
            CacheNode<K, V> node = cache.get(key);
            
            if (node != null) {
                // 更新现有节点
                node.value = value;
                if (expireTime > 0) {
                    node.expireTime = System.currentTimeMillis() + expireTime;
                }
                updateFrequency(node);
                return;
            }
            
            // 检查是否需要淘汰
            if (size >= capacity) {
                evict();
            }
            
            // 创建新节点
            node = new CacheNode<>(key, value);
            if (expireTime > 0) {
                node.expireTime = System.currentTimeMillis() + expireTime;
            }
            
            // 添加到频率为1的桶中
            addToFrequencyBucket(1, node);
            cache.put(key, node);
            minFrequency = 1;
            size++;
            
            statistics.incrementPuts();
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 更新节点频率
     * @param node 节点
     */
    private void updateFrequency(CacheNode<K, V> node) {
        // 从当前频率桶中移除
        FrequencyBucket<K, V> currentBucket = frequencyBuckets.get(node.frequency);
        if (currentBucket != null) {
            currentBucket.removeNode(node);
            
            // 如果当前频率是最小频率且桶为空,更新最小频率
            if (node.frequency == minFrequency && currentBucket.isEmpty()) {
                minFrequency++;
            }
        }
        
        // 增加频率
        node.frequency++;
        
        // 添加到新频率桶中
        addToFrequencyBucket(node.frequency, node);
    }
    
    /**
     * 添加节点到指定频率桶
     * @param frequency 频率
     * @param node 节点
     */
    private void addToFrequencyBucket(int frequency, CacheNode<K, V> node) {
        FrequencyBucket<K, V> bucket = frequencyBuckets.get(frequency);
        if (bucket == null) {
            bucket = new FrequencyBucket<>();
            frequencyBuckets.put(frequency, bucket);
        }
        bucket.addNode(node);
    }
    
    /**
     * 淘汰最少使用的节点
     */
    private void evict() {
        lock.lock();
        try {
            // 获取最小频率桶
            FrequencyBucket<K, V> bucket = frequencyBuckets.get(minFrequency);
            if (bucket == null || bucket.isEmpty()) {
                // 找到下一个非空桶
                for (int freq = minFrequency + 1; freq <= Collections.max(frequencyBuckets.keySet()); freq++) {
                    bucket = frequencyBuckets.get(freq);
                    if (bucket != null && !bucket.isEmpty()) {
                        minFrequency = freq;
                        break;
                    }
                }
                
                if (bucket == null || bucket.isEmpty()) {
                    return;
                }
            }
            
            // 移除最久未使用的节点(链表头部)
            CacheNode<K, V> nodeToEvict = bucket.removeFirst();
            if (nodeToEvict != null) {
                cache.remove(nodeToEvict.key);
                size--;
                
                // 通知监听器
                for (EvictionListener<K, V> listener : evictionListeners) {
                    listener.onEvicted(nodeToEvict.key, nodeToEvict.value, EvictionReason.LFU);
                }
                
                statistics.incrementEvictions();
            }
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 应用热度衰减
     */
    public void applyDecay() {
        lock.lock();
        try {
            long currentTime = System.currentTimeMillis();
            
            // 遍历所有频率桶
            for (Map.Entry<Integer, FrequencyBucket<K, V>> entry : frequencyBuckets.entrySet()) {
                int frequency = entry.getKey();
                FrequencyBucket<K, V> bucket = entry.getValue();
                
                // 计算衰减后的频率
                int newFrequency = (int) Math.max(1, frequency * decayFactor);
                
                if (newFrequency < frequency) {
                    // 将节点移动到新的频率桶
                    CacheNode<K, V> node = bucket.getFirst();
                    while (node != null) {
                        CacheNode<K, V> next = node.next;
                        
                        // 检查是否过期
                        if (node.expireTime > 0 && node.expireTime < currentTime) {
                            // 节点已过期,移除
                            bucket.removeNode(node);
                            cache.remove(node.key);
                            size--;
                            
                            // 通知监听器
                            for (EvictionListener<K, V> listener : evictionListeners) {
                                listener.onEvicted(node.key, node.value, EvictionReason.EXPIRED);
                            }
                            
                            statistics.incrementEvictions();
                        } else if (newFrequency != frequency) {
                            // 移动到新的频率桶
                            bucket.removeNode(node);
                            node.frequency = newFrequency;
                            addToFrequencyBucket(newFrequency, node);
                        }
                        
                        node = next;
                    }
                }
            }
            
            // 更新最小频率
            updateMinFrequency();
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 更新最小频率
     */
    private void updateMinFrequency() {
        for (int freq = 1; freq <= Collections.max(frequencyBuckets.keySet()); freq++) {
            FrequencyBucket<K, V> bucket = frequencyBuckets.get(freq);
            if (bucket != null && !bucket.isEmpty()) {
                minFrequency = freq;
                return;
            }
        }
    }
    
    /**
     * 移除指定键的缓存项
     * @param key 键
     * @return 被移除的值
     */
    public V remove(K key) {
        lock.lock();
        try {
            CacheNode<K, V> node = cache.remove(key);
            if (node == null) {
                return null;
            }
            
            // 从频率桶中移除
            FrequencyBucket<K, V> bucket = frequencyBuckets.get(node.frequency);
            if (bucket != null) {
                bucket.removeNode(node);
                
                if (node.frequency == minFrequency && bucket.isEmpty()) {
                    updateMinFrequency();
                }
            }
            
            size--;
            return node.value;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 清空缓存
     */
    public void clear() {
        lock.lock();
        try {
            cache.clear();
            frequencyBuckets.clear();
            minFrequency = 0;
            size = 0;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 获取缓存大小
     * @return 当前缓存大小
     */
    public int size() {
        return size;
    }
    
    /**
     * 获取缓存容量
     * @return 缓存容量
     */
    public int capacity() {
        return capacity;
    }
    
    /**
     * 设置缓存容量
     * @param newCapacity 新容量
     */
    public void setCapacity(int newCapacity) {
        lock.lock();
        try {
            if (newCapacity < capacity) {
                // 缩容,需要淘汰多余项
                while (size > newCapacity) {
                    evict();
                }
            }
            this.capacity = newCapacity;
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 获取热度衰减因子
     * @return 衰减因子
     */
    public double getDecayFactor() {
        return decayFactor;
    }
    
    /**
     * 设置热度衰减因子
     * @param decayFactor 衰减因子 (0,1]
     */
    public void setDecayFactor(double decayFactor) {
        if (decayFactor <= 0 || decayFactor > 1) {
            throw new IllegalArgumentException("Decay factor must be in range (0, 1]");
        }
        this.decayFactor = decayFactor;
    }
    
    /**
     * 获取时间窗口
     * @return 时间窗口(毫秒)
     */
    public long getTimeWindow() {
        return timeWindow;
    }
    
    /**
     * 设置时间窗口
     * @param timeWindow 时间窗口(毫秒)
     */
    public void setTimeWindow(long timeWindow) {
        this.timeWindow = timeWindow;
    }
    
    /**
     * 添加淘汰监听器
     * @param listener 监听器
     */
    public void addEvictionListener(EvictionListener<K, V> listener) {
        evictionListeners.add(listener);
    }
    
    /**
     * 移除淘汰监听器
     * @param listener 监听器
     */
    public void removeEvictionListener(EvictionListener<K, V> listener) {
        evictionListeners.remove(listener);
    }
    
    /**
     * 获取统计信息
     * @return 统计信息
     */
    public CacheStatistics getStatistics() {
        return statistics;
    }
    
    /**
     * 获取所有键
     * @return 键集合
     */
    public Set<K> keySet() {
        return cache.keySet();
    }
    
    /**
     * 检查键是否存在
     * @param key 键
     * @return 是否存在
     */
    public boolean containsKey(K key) {
        return cache.containsKey(key);
    }
    
    /**
     * 缓存节点类
     */
    private static class CacheNode<K, V> {
        K key;
        V value;
        int frequency;
        long expireTime; // 过期时间戳,-1表示永不过期
        CacheNode<K, V> prev;
        CacheNode<K, V> next;
        
        CacheNode(K key, V value) {
            this.key = key;
            this.value = value;
            this.frequency = 1;
            this.expireTime = -1;
        }
    }
    
    /**
     * 频率桶类
     */
    private static class FrequencyBucket<K, V> {
        private CacheNode<K, V> head;
        private CacheNode<K, V> tail;
        private int size;
        
        FrequencyBucket() {
            // 创建虚拟头尾节点
            head = new CacheNode<>(null, null);
            tail = new CacheNode<>(null, null);
            head.next = tail;
            tail.prev = head;
            size = 0;
        }
        
        /**
         * 添加节点到尾部(最近使用)
         */
        void addNode(CacheNode<K, V> node) {
            node.prev = tail.prev;
            node.next = tail;
            tail.prev.next = node;
            tail.prev = node;
            size++;
        }
        
        /**
         * 移除指定节点
         */
        void removeNode(CacheNode<K, V> node) {
            if (node.prev != null && node.next != null) {
                node.prev.next = node.next;
                node.next.prev = node.prev;
                node.prev = null;
                node.next = null;
                size--;
            }
        }
        
        /**
         * 移除第一个节点(最久未使用)
         */
        CacheNode<K, V> removeFirst() {
            if (head.next == tail) {
                return null;
            }
            CacheNode<K, V> first = head.next;
            removeNode(first);
            return first;
        }
        
        /**
         * 获取第一个节点
         */
        CacheNode<K, V> getFirst() {
            return head.next == tail ? null : head.next;
        }
        
        /**
         * 判断桶是否为空
         */
        boolean isEmpty() {
            return size == 0;
        }
        
        /**
         * 获取桶大小
         */
        int size() {
            return size;
        }
    }
    
    /**
     * 淘汰原因枚举
     */
    public enum EvictionReason {
        LFU,        // 最少使用
        EXPIRED,    // 过期
        MANUAL      // 手动移除
    }
    
    /**
     * 淘汰监听器接口
     */
    public interface EvictionListener<K, V> {
        void onEvicted(K key, V value, EvictionReason reason);
    }
    
    /**
     * 缓存统计信息类
     */
    public static class CacheStatistics {
        private long hits;
        private long misses;
        private long puts;
        private long evictions;
        private long lastDecayTime;
        
        public CacheStatistics() {
            this.hits = 0;
            this.misses = 0;
            this.puts = 0;
            this.evictions = 0;
            this.lastDecayTime = System.currentTimeMillis();
        }
        
        public void incrementHits() {
            hits++;
        }
        
        public void incrementMisses() {
            misses++;
        }
        
        public void incrementPuts() {
            puts++;
        }
        
        public void incrementEvictions() {
            evictions++;
        }
        
        public long getHits() {
            return hits;
        }
        
        public long getMisses() {
            return misses;
        }
        
        public long getPuts() {
            return puts;
        }
        
        public long getEvictions() {
            return evictions;
        }
        
        public long getTotalRequests() {
            return hits + misses;
        }
        
        public double getHitRate() {
            long total = getTotalRequests();
            return total == 0 ? 0 : (double) hits / total;
        }
        
        public double getMissRate() {
            long total = getTotalRequests();
            return total == 0 ? 0 : (double) misses / total;
        }
        
        public long getLastDecayTime() {
            return lastDecayTime;
        }
        
        public void updateLastDecayTime() {
            this.lastDecayTime = System.currentTimeMillis();
        }
        
        @Override
        public String toString() {
            return String.format(
                "CacheStatistics{hits=%d, misses=%d, puts=%d, evictions=%d, hitRate=%.2f%%}",
                hits, misses, puts, evictions, getHitRate() * 100);
        }
    }
}

2. 创建一个监控服务类,用于监控LFU缓存的性能和淘汰策略

bash 复制代码
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * LFU缓存监控服务
 * 提供热度衰减曲线和淘汰策略监控功能
 */
public class LFUCacheMonitor<K, V> {
    
    private final LFUCache<K, V> cache;
    private final ScheduledExecutorService scheduler;
    private final Consumer<LFUCache.CacheStatistics> statisticsConsumer;
    
    // 监控任务
    private Runnable monitoringTask;
    
    // 衰减任务
    private Runnable decayTask;
    
    public LFUCacheMonitor(LFUCache<K, V> cache) {
        this(cache, null);
    }
    
    public LFUCacheMonitor(LFUCache<K, V> cache, Consumer<LFUCache.CacheStatistics> statisticsConsumer) {
        this.cache = cache;
        this.scheduler = Executors.newScheduledThreadPool(2);
        this.statisticsConsumer = statisticsConsumer != null ? statisticsConsumer : this::defaultStatisticsHandler;
        
        initializeTasks();
    }
    
    /**
     * 初始化监控任务
     */
    private void initializeTasks() {
        // 统计信息监控任务
        monitoringTask = () -> {
            LFUCache.CacheStatistics stats = cache.getStatistics();
            statisticsConsumer.accept(stats);
        };
        
        // 热度衰减任务
        decayTask = () -> {
            cache.applyDecay();
            cache.getStatistics().updateLastDecayTime();
        };
    }
    
    /**
     * 默认统计信息处理器
     */
    private void defaultStatisticsHandler(LFUCache.CacheStatistics stats) {
        System.out.println("[" + System.currentTimeMillis() + "] " + stats.toString());
    }
    
    /**
     * 开始监控
     * @param period 监控周期(秒)
     */
    public void startMonitoring(int period) {
        scheduler.scheduleAtFixedRate(monitoringTask, period, period, TimeUnit.SECONDS);
    }
    
    /**
     * 开始热度衰减
     * @param period 衰减周期(秒)
     */
    public void startDecay(int period) {
        scheduler.scheduleAtFixedRate(decayTask, period, period, TimeUnit.SECONDS);
    }
    
    /**
     * 停止监控
     */
    public void stop() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
    
    /**
     * 获取当前统计信息
     * @return 统计信息
     */
    public LFUCache.CacheStatistics getCurrentStatistics() {
        return cache.getStatistics();
    }
    
    /**
     * 重置统计信息
     */
    public void resetStatistics() {
        // 注意:实际实现中可能需要在LFUCache中添加重置方法
        // 这里仅作示意
    }
}

3. 创建一个示例和测试类

Java 复制代码
package com.ty.components.cachemanager.core;

import java.util.Random;

/**
 * LFU缓存使用示例
 */
public class LFUCacheExample {
    
    public static void main(String[] args) throws InterruptedException {
        // 创建LFU缓存,容量为10
        LFUCache<String, String> cache = new LFUCache<>(10);
        
        // 添加淘汰监听器
        cache.addEvictionListener((key, value, reason) -> 
            System.out.println("Evicted: " + key + " -> " + value + " (Reason: " + reason + ")")
        );
        
        // 创建监控器
        LFUCacheMonitor<String, String> monitor = new LFUCacheMonitor<>(cache, stats -> 
            System.out.println("Stats: " + stats.toString())
        );
        
        // 启动监控,每5秒输出一次统计信息
        monitor.startMonitoring(5);
        
        // 启动衰减,每10秒执行一次热度衰减
        monitor.startDecay(10);
        
        // 模拟缓存使用
        simulateCacheUsage(cache);
        
        // 等待一段时间观察监控输出
        Thread.sleep(30000);
        
        // 停止监控
        monitor.stop();
    }
    
    private static void simulateCacheUsage(LFUCache<String, String> cache) {
        Random random = new Random();
        String[] keys = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"};
        
        // 启动一个线程模拟并发访问
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                // 随机访问缓存项
                String key = keys[random.nextInt(keys.length)];
                String value = cache.get(key);
                
                if (value == null) {
                    // 如果不存在,则添加
                    cache.put(key, "Value-" + key, 30000); // 30秒过期
                }
                
                try {
                    Thread.sleep(random.nextInt(500)); // 随机休眠
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }).start();
        
        // 启动另一个线程,高频访问某些项
        new Thread(() -> {
            for (int i = 0; i < 200; i++) {
                // 高频访问A、B、C
                String key = keys[random.nextInt(3)]; // 只访问前3个键
                String value = cache.get(key);
                
                if (value == null) {
                    cache.put(key, "Value-" + key, 30000);
                }
                
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }).start();
    }
}

以上实现了一个完整的支持动态调整的LFU缓存系统,具有以下特性:

  1. LFU淘汰策略:基于访问频率的缓存淘汰机制
  2. 动态调整:
    可调整缓存容量
    可配置热度衰减因子
    可设置时间窗口
  3. 热度衰减曲线:
    定期应用热度衰减
    支持过期时间检查
    可配置衰减因子
  4. 淘汰策略监控:
    提供详细的统计信息(命中率、未命中率等)
    支持淘汰监听器
    实时监控缓存性能
  5. 其他特性:
    线程安全
    支持过期时间
    易于集成和扩展
    这个实现可以很好地集成到现有的缓存管理系统中,并提供比现有Redis缓存更高级的功能
相关推荐
94甘蓝2 小时前
第 5 篇 Spring AI - Tool Calling 全面解析:从基础到高级应用
java·人工智能·函数调用·工具调用·spring ai·tool calling
酉鬼女又兒2 小时前
SQL113+114 更新记录(一)(二)+更新数据知识总结
java·服务器·前端
毅炼2 小时前
Netty 常见问题总结
java·网络·数据结构·算法·哈希算法
Anastasiozzzz3 小时前
leetcodehot100--最小栈 MinStack
java·javascript·算法
Sylvia-girl3 小时前
线程的死锁【了解】
java·开发语言·jvm
Elias不吃糖3 小时前
java开发的三层架构
java·开发语言·架构
pp起床3 小时前
【苍穹外卖】Day2.5 分类管理
java
派大鑫wink3 小时前
【Day57】SpringBoot 整合 Redis:吃透缓存配置与 API 实战
spring boot·redis·缓存
lixin5565563 小时前
基于神经网络的音乐生成增强器
java·人工智能·pytorch·python·深度学习·语言模型