Java 定期缓存
本文最后更新于 947 天前,其中的信息可能已经有所发展或是发生改变。
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
 * 定期缓存,参考Redis的过期策略实现
 */
public class ExpireCache {
    /**
     * 默认每隔30秒随机扫描过期key
     */
    public static final long DEFAULT_PERIOD_SECOND = 30;
    /**
     * 每次最多扫描key的数量
     */
    public static final int MAX_SCAN_SIZE = 20;
    /**
     * 定时任务
     */
    private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    /**
     * 线程安全的缓存
     */
    private final Map> map = new ConcurrentHashMap<>();
    /**
     * 创建默认过期缓存,默认每隔30秒随机删除最多20个过期的key
     */
    public ExpireCache() {
        this(DEFAULT_PERIOD_SECOND, TimeUnit.SECONDS);
    }
    /**
     * 创建过期缓存,默认每隔30秒随机删除最多20个过期的key
     *
     * @param period   定时扫描过期key的时间间隔
     * @param timeUnit 时间单位
     */
    public ExpireCache(long period, TimeUnit timeUnit) {
        executor.scheduleAtFixedRate(this::scanClean, period, period, timeUnit);
    }
    /**
     * 存放一个不过期的key
     *
     * @param key
     * @param value
     */
    public void put(String key, T value) {
        map.put(key, new DelayValue<>(value));
    }
    /**
     * 存放一个过期的key
     *
     * @param key
     * @param value
     * @param delay 过期时间(单位:ms)
     */
    public void put(String key, T value, long delay) {
        map.put(key, new DelayValue<>(value, delay));
    }
    /**
     * 获取一个未过期的key
     * 惰性删除:若key已过期则删除并返回null
     *
     * @param key
     */
    public T get(String key) {
        DelayValue value = map.get(key);
        if (Objects.isNull(value)) {
            return null;
        }
        if (expired(value)) {
            map.remove(key);
            return null;
        }
        return value.data;
    }
    /**
     * 随机扫描20个key,若过期则删除
     */
    private void scanClean() {
        List canExpiredKeys = map.entrySet().stream().filter(e -> canExpired(e.getValue())).map(Map.Entry::getKey).collect(Collectors.toList());
        Collections.shuffle(canExpiredKeys);
        for (int i = 0, end = Math.min(canExpiredKeys.size(), MAX_SCAN_SIZE); i < end; i++) {
            String key = canExpiredKeys.get(i);
            DelayValue value = map.get(key);
            if (Objects.nonNull(value) && expired(value)) {
                map.remove(key);
            }
        }
    }
    /**
     * 判断value是否有过期时间
     */
    private boolean canExpired(DelayValue value) {
        return value.delay != DelayValue.FOREVER_FLAG;
    }
    /**
     * 判断value是否过期
     */
    private boolean expired(DelayValue value) {
        return value.delay != DelayValue.FOREVER_FLAG && (System.currentTimeMillis() > value.timestamp + value.delay);
    }
    /**
     * 存储过期value
     */
    private static class DelayValue {
        /**
         * -1表示永不过期
         */
        static final long FOREVER_FLAG = -1;
        /**
         * 存储的数据
         */
        final T data;
        /**
         * 创建value的起始时间
         */
        final long timestamp = System.currentTimeMillis();
        /**
         * 过期时间(单位:ms)
         */
        final long delay;
        DelayValue(T data) {
            this.data = data;
            this.delay = FOREVER_FLAG;
        }
        DelayValue(T data, long delay) {
            this.data = data;
            this.delay = delay;
        }
    }
}
如果觉得本文对您有帮助,记得收藏哦~
上一篇
下一篇