C# 实现 key-value 结构自定义缓存 CustomCache

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# 实现 key-value 结构自定义缓存 CustomCache相关的知识,希望对你有一定的参考价值。

功能需求

使用 C# 编写一个 key-value 结构进程内缓存,实现数据缓存的基本操作,此处所用到的知识点如下:

  • 线程安全的字典 ConcurrentDictionary

  • 设计模式之单例模式(Singleton);

  • 缓存数据【主动 & 被动】过期模式;

cache

key-value 缓存实现

说明:此处基于 .net 6 平台创建控制台项目。

  • 新建 ConsoleApp 项目,添加 CustomCacheHelper.cs 类;

  1. 导入命名空间(namespace

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
  1. 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=key2value 值为空,说明我们内部调用 Exist 方法生效了,key2value 缓存有效时间是 3 秒,第二次打印输出信息时,此时已经睡眠 5 秒,相对于 key2 存储的内存数据已经超时,而 key3value 存储的有效时间是 6 秒,没有超时,所以能个获取到对应的内存数据。

启动测试

通过上面的 demo 演示,我们就实现了一个自定义的进程内缓存助手,在项目中可以很方便的导入使用。

以上是关于C# 实现 key-value 结构自定义缓存 CustomCache的主要内容,如果未能解决你的问题,请参考以下文章

面向HBase的内存key-value缓存的实现

C# 滚动自定义图形以减少闪烁的更好方法

MySQL与Redis实现二级缓存

Redis学习

C# 自定义迭代器实现

C# WinForm开发