功能需求
使用 C#
编写一个 key-value
结构进程内缓存,实现数据的缓存操作,此处所用到的知识点如下:
- 线程安全的字典
ConcurrentDictionary
; - 设计模式之单例模式(
Singleton
); - 缓存数据【主动 & 被动】过期模式;
key-value 缓存实现
说明:此处基于 .net 6 平台创建控制台项目。
- 新建
ConsoleApp
项目,添加CustomCacheHelper.cs
类;
- 导入命名空间(
namespace
)
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
CustomCacheHelper.cs
类中编写实现方法
/// <summary>
/// 自定义内存缓存助手
/// </summary>
public sealed class CustomCacheHelper
{
#region 单例模式
//创建私有化静态obj锁
private static readonly object _ObjLock = new();
//创建私有静态字段,接收类的实例化对象
private static volatile CustomCacheHelper? _Instance = null;
//构造函数私有化
private CustomCacheHelper() { }
//创建单利对象资源并返回
public static CustomCacheHelper GetSingleObj()
{
if (_Instance == null)
{
lock (_ObjLock)
{
if (_Instance == null)
{
_Instance = new CustomCacheHelper();
}
}
}
return _Instance;
}
#endregion
/// <summary>
/// 缓存字典 => 【key|value|time】
/// </summary>
private static volatile ConcurrentDictionary<string, KeyValuePair<object, DateTime?>> _CacheDictionary = new();
/// <summary>
/// 1.主动过期
/// </summary>
static CustomCacheHelper()
{
Task.Run(() => {
while (true)
{
int millisecondsTimeout = 1000 * 60 * 10;
Thread.Sleep(millisecondsTimeout); //10分钟检查一次
if (_CacheDictionary != null && _CacheDictionary.Keys.Count > 0)
{
ICollection<string> listKey = _CacheDictionary.Keys;
foreach (var key in listKey)
{
var valueTime = _CacheDictionary[key];
if (valueTime.Value < DateTime.Now)
{
_CacheDictionary.TryRemove(key, out KeyValuePair<object, DateTime?> value);
}
}
}
}
});
}
/// <summary>
/// 索引器
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public object this[string key]
{
get => _CacheDictionary[key];
set => _CacheDictionary[key] = new KeyValuePair<object, DateTime?>(value, null);
}
/// <summary>
/// 设置相对过期缓存
/// </summary>
/// <param name="key">键</param>
/// <param name="data">数据包</param>
/// <param name="seconds">相对过期时间</param>
public void Set(string key, object data, int seconds)
{
var expirationTime = DateTime.Now.AddSeconds(seconds);
_CacheDictionary[key] = new KeyValuePair<object, DateTime?>(data, expirationTime);
}
/// <summary>
/// 设置绝对过期缓存
/// </summary>
/// <param name="key">键<</param>
/// <param name="data">数据包</param>
public void Set(string key, object data)
{
this[key] = data; // 下面代码等效
// _CacheDictionary[key] = new KeyValuePair<object, DateTime?>(data, null);
}
/// <summary>
/// 通过key获取缓存value
/// 2.被动过期
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public T? Get<T>(string key)
{
if (Exist(key))
{
//var valueTime = _CacheDictionary[key];
//return (T)valueTime.Key; //return (T)this[key];
bool hasValue = _CacheDictionary.TryGetValue(key, out KeyValuePair<object, DateTime?> value);
if (hasValue)
{
return (T)value.Key; //return (T)this[key];
}
}
return default;
}
/// <summary>
/// 获取所有的key
/// </summary>
/// <returns></returns>
public ICollection<string> GetKeys() => _CacheDictionary.Keys;
/// <summary>
/// 获取缓存个数
/// </summary>
/// <returns></returns>
public int Count()
{
int count = 0;
if (_CacheDictionary != null)
{
count = _CacheDictionary.Count;
}
return count;
}
/// <summary>
/// 删除指定key的value
/// </summary>
/// <param name="key"></param>
public void Remove(string key)
{
if (Exist(key))
{
_CacheDictionary.TryRemove(key, out KeyValuePair<object, DateTime?> value);
}
}
/// <summary>
/// 清空所有缓存
/// </summary>
public void Cleaner()
{
if (_CacheDictionary != null && _CacheDictionary.Count > 0)
{
foreach (var key in _CacheDictionary.Keys)
{
_CacheDictionary.TryRemove(key, out KeyValuePair<object, DateTime?> value);
}
}
}
/// <summary>
/// 2.被动过期,保证任何过期缓存都无法取值
/// 检查key是否存在
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool Exist(string key)
{
bool isExist = false;
if (!string.IsNullOrWhiteSpace(key) && _CacheDictionary.ContainsKey(key))
{
var valTime = _CacheDictionary[key];
if (valTime.Value != null && valTime.Value > DateTime.Now)
{
isExist = true; //缓存没过期
}
else
{
if (valTime.Value == null)
{
isExist = true; //永久缓存
}
else
{
_CacheDictionary.TryRemove(key, out KeyValuePair<object, DateTime?> value); //缓存过期清理
}
}
}
return isExist;
}
}
Main 方法使用缓存
由于该项目是采用控制台程序编写,我们可直接在 Main
方法中,添加如下代码:
var customCache = CustomCacheHelper.GetSingleObj();
customCache.Set("key1", "value1");
customCache.Set("key2", "value2", 3);
customCache.Set("key3", "value3", 6);
var keys = customCache.GetKeys();
Console.WriteLine("首次打印:");
foreach (var key in keys)
{
Console.WriteLine($"time:{DateTime.Now},key={key},value={customCache.Get<string>(key)}");
}
Console.WriteLine("睡眠5s后再次打印:");
Thread.Sleep(5000);
foreach (var key in keys)
{
Console.WriteLine($"time:{DateTime.Now},key={key},value={customCache.Get<string>(key)}");
}
此处代码中我们添加了三组 key-vaule
数据,其中一个是没有设置过期时间,另外两个设置过期时间,保存数据后,分别打印缓存中保存的数据,再第二次缓存打印前,先让线程睡眠等待 5 秒(5000毫秒)
,注意观察控制台输出的信息。
ConsoleApp 启动测试
从控制台输出的信息中,我们可以看到 key=key2
的 value
值为空,说明我们内部调用 Exist
方法生效了,key2
的 value
缓存有效时间是 3
秒,第二次打印输出信息时,此时已经睡眠 5
秒,相对于 key2
存储的内存数据已经超时,而 key3
的 value
存储的有效时间是 6
秒,没有超时,所以能个获取到对应的内存数据。
通过上面的 demo
演示,我们就实现了一个自定义的进程内缓存助手,在项目中可以很方便的导入使用。