简简单单做一个带过期时间的内存缓存

做手机验证码的时候,一般都有五分钟或十分钟的限时机制,所以就需要把“号码–验证码”的信息暂存起来,过期便无效——类似于 Redis 自带过期的机制就适合了。不过应用 Redis 此类缓存模块要专门搭建环境和配置——比较繁琐,于是想到用 JVM 的缓存来做。关键地,我参考了该资源:

https://blog.csdn.net/wab719591157/article/details/78029861

并在这个基础上重写一遍,主要是改造为我自己的编码风格(例如泛型的处理,函数式 Lambda 代替 Interface 等),并且加入缓存自动失效的功能。实际上原作者已经足够简单了,仅仅60代码却清晰明了地说明问题,非常不错。

在安排周期性执行任务的问题上,有网友评论说用 ScheduledExecutorService 实现。我觉得对于这类单个线程的定时器,简单用 Timer + TimerTask便足够了。如果实在不行敬请大家提出。

源码整合在我的 AJAXJS 框架中:https://gitee.com/sp42_admin/ajaxjs/tree/master/ajaxjs-base/src/main/java/com/ajaxjs/util/cache

ExpireCacheData

存入数据的时候把存入的实际数据增加一个外包装,顺便加上存入时间,和过期时间。

package com.ajaxjs.util.cache;

import java.util.Date;
import java.util.function.Supplier;

/**
 * 
 * 缓存数据。存入数据的时候把存入的实际数据增加一个外包装,顺便加上存入时间,和过期时间
 * 
 * @author sp42 frank@ajaxjs.com
 *
 * @param <T> 缓存类型,可以是任意类型
 */
public class ExpireCacheData<T> {
	/**
	 * 创建缓存数据
	 * 
	 * @param t      缓存的数据,可以是任意类型的对象
	 * @param expire 过期时间,单位是秒
	 */
	ExpireCacheData(T t, int expire) {
		this.data = t;
		this.expire = expire <= 0 ? 0 : expire * 1000;
		this.saveTime = new Date().getTime() + this.expire;
	}

	/**
	 * 创建缓存数据
	 * 
	 * @param t      缓存的数据,可以是任意类型的对象
	 * @param expire 过期时间,单位是秒
	 * @param load   数据装载器
	 */
	ExpireCacheData(T t, int expire, Supplier<T> load) {
		this(t, expire);
		this.load = load;
	}

	/**
	 * 缓存的数据,可以是任意类型的对象
	 */
	public T data;

	/**
	 * 过期时间 小于等于0标识永久存活
	 */
	public long expire;

	/**
	 * 存活时间
	 */
	public long saveTime;

	/**
	 * 还可以增加一个数据装载器,如果缓存中没有数据或者已经过期,则调用数据装载器加载最新的数据并且加入缓存,并返回。
	 */
	public Supplier<T> load;
}

ExpireCache 管理器

我喜欢用继承,于是 ExpireCache 继承了 ConcurrentHashMap,本质上它依然是个 Map,自然有 get/put 方法,也相当于一个非常简单的内存缓存了。而且它本身承托了一个 static 对象 CACHE,这里权且当作单例使用。

package com.ajaxjs.util.cache;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

/**
 * 带过期时间的内存缓存。 参考:https://blog.csdn.net/wab719591157/article/details/78029861
 * 
 * @author sp42 frank@ajaxjs.com
 *
 */
public class ExpireCache extends ConcurrentHashMap<String, ExpireCacheData<Object>> {
	private static final long serialVersionUID = 3850668473354271847L;

	/**
	 * 单例,外界一般调用该对象的方法
	 */
	public final static ExpireCache CACHE = new ExpireCache();

	static {
		// 缓存自动失效
		// 周期性执行任务,另外可以考虑 ScheduledExecutorService
		new Timer().schedule(new TimerTask() {
			@Override
			public void run() {
				for (String key : CACHE.keySet()) {
					Object obj = CACHE.get(key);

					/*
					 * 超时了,干掉!另外:有 load 的总是不会返回 null,所以这里不用考虑有 load 的 ExpireCacheData。 而且有 load
					 * 的也不应该被 kill
					 */
					if (obj == null)
						CACHE.remove(key);
				}
			}
		}, 0, 10 * 1000); // 10 秒钟清理一次
	}

	/**
	 * 获取缓存中的数据
	 * 
	 * @param key 缓存 KEY
	 * @return 缓存的数据,找不到返回或者过期则直接返回 null
	 */
	public Object get(String key) {
		ExpireCacheData<Object> data = super.get(key);
		long now = new Date().getTime();

		if (data != null && (data.expire <= 0 || data.saveTime >= now))
			return data.data;
		else if (data.load != null) {
			Object value = data.load.get();
			data.data = value;
			data.saveTime = now + data.expire; // 重新算过存活时间

			return value;
		}

		return null;
	}

	/**
	 * 获取缓存中的数据(避免强类型转换的麻烦)
	 * 
	 * @param <T> 缓存的类型
	 * @param key 缓存 KEY
	 * @param clz 缓存的类型
	 * @return 缓存的数据,找不到返回或者过期则直接返回 null
	 */
	@SuppressWarnings({ "unchecked" })
	public <T> T get(String key, Class<T> clz) {
		Object obj = get(key);
		return obj == null ? null : (T) obj;
	}

	/**
	 * 
	 * @param key    缓存 KEY
	 * @param data   要缓存的数据
	 * @param expire 过期时间
	 */
	public void put(String key, Object data, int expire) {
		put(key, new ExpireCacheData<>(data, expire));
	}

	/**
	 * 
	 * @param key    缓存 KEY
	 * @param data   要缓存的数据
	 * @param expire 过期时间
	 * @param load   数据装载器,如果缓存中没有数据或者已经过期,则调用数据装载器加载最新的数据并且加入缓存,并返回
	 */
	public void put(String key, Object data, int expire, Supplier<Object> load) {
		put(key, new ExpireCacheData<>(data, expire, load));
	}

}

例子

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import org.junit.Test;

import com.ajaxjs.util.cache.ExpireCache;

public class TestCache {
	@Test
	public void testExpire() throws InterruptedException {
		ExpireCache.CACHE.put("foo", "bar", 3);

		Thread.sleep(1000);
		assertEquals("bar", ExpireCache.CACHE.get("foo"));

		Thread.sleep(2010);
		assertNull(ExpireCache.CACHE.get("foo"));

		ExpireCache.CACHE.put("bar", "foo", 2, () -> "foo-2");

		Thread.sleep(1000);
		assertEquals("foo", ExpireCache.CACHE.get("bar"));

		Thread.sleep(2010);
		assertEquals("foo-2", ExpireCache.CACHE.get("bar"));
	}
}

©️2020 CSDN 皮肤主题: 岁月 设计师:pinMode 返回首页