关于全局缓存的一种简单实现方法

缓存,在.Net系统开发中,经常使用到。如果,让你自己去实现,你会怎么做呢。

开始编码前思考:

 1、肯定 是要 根据 key 去查询对应value,so 应该是List<KeyValuePair> 这类集合做缓存载体;

 2、肯定是要全局公用,so 缓存的列表应该唯一;

 3、应该有支持并发的能力,so 实现过程中肯定要加锁; 

 4、应该可以支持过期,自动 or 手动,so 应该 可以添加过期时间;

好的,基于以上3点,设计的类应该需要是单体(单例)模式,查询、删除key时应该lock 缓存载体,可以有定时清除过期缓存项的能力;

  1     public static class CacheHelper
  2     {
  3         private static int RestoreCacheCount = 20000;
  4         private static DateTime LastRestoreCachTime;
  5         private static Dictionary<string, MyCacheEntity> CacheEntryDictionary;
  6         private static readonly object objLock = new object();
  7 
  8         static CacheHelper()
  9         {
 10             if (CacheEntryDictionary == null)
 11             {
 12                 lock (objLock)
 13                 {
 14                     if (CacheEntryDictionary == null)
 15                     {
 16                         CacheEntryDictionary = new Dictionary<string, MyCacheEntity>();
 17                         LastRestoreCachTime = DateTime.Now;
 18                         System.Threading.Tasks.Task.Factory.StartNew(() => RestoreCache());
 19                     }
 20                 }
 21             }
 22         }
 23 
 24         public static void Set<T>(string key, T entity, double timeout)
 25         {
 26             if (string.IsNullOrEmpty(key))
 27                 throw new ArgumentNullException(nameof(key));
 28             if (entity == null)
 29                 throw new ArgumentNullException(nameof(entity));
 30             var expiredTime = DateTime.Now.AddSeconds(timeout);
 31             lock (objLock)
 32             {
 33                 if (DateTime.Now.Subtract(expiredTime).TotalSeconds >= 0)
 34                 {
 35                     if (CacheEntryDictionary.ContainsKey(key))
 36                         CacheEntryDictionary.Remove(key);
 37                 }
 38                 else
 39                 {
 40                     CacheEntryDictionary[key] = new MyCacheEntity { Value = entity, ExpiredTime = expiredTime };
 41                 }
 42             }
 43         }
 44 
 45         public static T Get<T>(string key)
 46         {
 47             if (string.IsNullOrEmpty(key))
 48                 throw new ArgumentNullException(nameof(key));
 49             var value = default(T);
 50             lock (objLock)
 51             {
 52                 var exist = CacheEntryDictionary.ContainsKey(key);
 53                 if (!exist) return value;
 54                 var obj = CacheEntryDictionary[key];
 55                 if (obj == null)
 56                 {
 57                     CacheEntryDictionary.Remove(key);
 58                     return value;
 59                 }
 60                 if (obj.ExpiredTime == DateTime.MinValue || DateTime.Now.Subtract(obj.ExpiredTime).TotalSeconds >= 0)
 61                 {
 62                     CacheEntryDictionary.Remove(key);
 63                     return value;
 64                 }
 65                 return (T)Convert.ChangeType(obj.Value, typeof(T));
 66             }
 67         }
 68 
 69         public static void Remove(string key)
 70         {
 71             if (string.IsNullOrEmpty(key))
 72                 throw new ArgumentNullException(nameof(key));
 73             try
 74             {
 75                 lock (objLock)
 76                 {
 77                     var exist = CacheEntryDictionary.ContainsKey(key);
 78                     if (!exist) return;
 79                     CacheEntryDictionary.Remove(key);
 80                 }
 81             }
 82             catch (Exception e)
 83             {
 84                 throw e;
 85             }
 86         }
 87 
 88         public static void Clear()
 89         {
 90             try
 91             {
 92                 lock (objLock)
 93                 {
 94                     CacheEntryDictionary.Clear();
 95                     LastRestoreCachTime = DateTime.Now;
 96                 }
 97             }
 98             catch (Exception e)
 99             {
100                 throw e;
101             }
102         }
103 
104         public static int Count => CacheEntryDictionary?.Count ?? 0;
105 
106         private static void RestoreCache()
107         {
108             while (true)
109             {
110                 if (CacheEntryDictionary.Keys.Count < 1) return;
111                 try
112                 {
113                     bool isEnter = false;
114                     Monitor.TryEnter(CacheEntryDictionary, 1000, ref isEnter);
115                     if (isEnter)
116                     {
117                         if (CacheEntryDictionary.Count > RestoreCacheCount && DateTime.Now.Subtract(LastRestoreCachTime).TotalMinutes > 1)
118                         {
119                             var keys = CacheEntryDictionary.Where(m => m.Value.ExpiredTime < DateTime.Now).Select(m => m.Key).ToList();
120                             foreach (var key in keys)
121                             {
122                                 CacheEntryDictionary.Remove(key);
123                             }
124                             LastRestoreCachTime = DateTime.Now;
125                         }
126                     }
127                 }
128                 finally
129                 {
130                     Monitor.Exit(CacheEntryDictionary);
131                 }
132                 Thread.Sleep(1000 * 6);
133             }
134         }
135     }
136     public class MyCacheEntity
137     {
138         public object Value { get; set; }
139         public DateTime ExpiredTime { get; set; }
140     }

如果想要 key 不区分大小写,可以 在构造函数中 设置

 CacheEntryDictionary = new Dictionary<string, MyCacheEntity>(StringComparer.OrdinalIgnoreCase);

你可能会有疑问,为什么不用 线程安全的字典 ConcurrentDictionary 呢?

当然可以将 Dictionary 改为 ConcurrentDictionary 没有任何问题,只是我想要自己手动实现加减速的过程;ConcurrentDictionary 字典内部也是调用Monitor 进行的加减锁的过程;

如果想要在缓存中实现 缓存文件,并且文件内容改变同步修改缓存的内容呢?

修改 以上方法 加入

 1、添加缓存文件

 2、刷新缓存文件内容,定时过期、监控文件内容更改等;

相关代码;

  1     public static class CacheHelper
  2     {
  3         private static int RestoreCacheCount = 20000;
  4         private static DateTime LastRestoreCachTime;
  5         private static Dictionary<string, MyCacheEntity> CacheEntryDictionary;
  6         private static readonly object objLock = new object();
  7         private static Dictionary<string, MyCacheEntity> FileCacheEntryDictionary;
  8 
  9         static CacheHelper()
 10         {
 11             if (CacheEntryDictionary == null)
 12             {
 13                 lock (objLock)
 14                 {
 15                     if (CacheEntryDictionary == null)
 16                     {
 17                         CacheEntryDictionary = new Dictionary<string, MyCacheEntity>(StringComparer.OrdinalIgnoreCase);
 18                         LastRestoreCachTime = DateTime.Now;
 19                         System.Threading.Tasks.Task.Factory.StartNew(() => RestoreCache());
 20                     }
 21                     if (FileCacheEntryDictionary == null)
 22                     {
 23                         FileCacheEntryDictionary = new Dictionary<string, MyCacheEntity>();
 24                         System.Threading.Tasks.Task.Factory.StartNew(() => ReFreshFileCache());
 25                     }
 26                 }
 27             }
 28         }
 29 
 30         public static void Set<T>(string key, T entity, double timeout)
 31         {
 32             if (string.IsNullOrEmpty(key))
 33                 throw new ArgumentNullException(nameof(key));
 34             if (entity == null)
 35                 throw new ArgumentNullException(nameof(entity));
 36             var expiredTime = DateTime.Now.AddSeconds(timeout);
 37             lock (objLock)
 38             {
 39                 if (DateTime.Now.Subtract(expiredTime).TotalSeconds >= 0)
 40                 {
 41                     if (CacheEntryDictionary.ContainsKey(key))
 42                         CacheEntryDictionary.Remove(key);
 43                 }
 44                 else
 45                 {
 46                     CacheEntryDictionary[key] = new MyCacheEntity { Value = entity, ExpiredTime = expiredTime };
 47                 }
 48             }
 49         }
 50 
 51         public static void SetFile(string key, string filePath, Encoding encoding, double timeout)
 52         {
 53             if (string.IsNullOrEmpty(key))
 54                 throw new ArgumentNullException(nameof(key));
 55             if (!File.Exists(filePath))
 56                 throw new FileNotFoundException(filePath);
 57             var expiredTime = DateTime.Now.AddSeconds(timeout);
 58             lock (objLock)
 59             {
 60                 if (DateTime.Now.Subtract(expiredTime).TotalSeconds >= 0)
 61                 {
 62                     if (CacheEntryDictionary.ContainsKey(key))
 63                         CacheEntryDictionary.Remove(key);
 64                 }
 65                 else
 66                 {
 67                     FileInfo fileInfo = new FileInfo(filePath);
 68                     string value = null;
 69                     using (var stream = new StreamReader(filePath, encoding))
 70                     {
 71                         value = stream.ReadToEnd();
 72                     }
 73                     CacheEntryDictionary[key] = new MyCacheEntity
 74                     {
 75                         Value = value,
 76                         ExpiredTime = expiredTime
 77                     };
 78                     FileCacheEntryDictionary[key] = new MyCacheEntity
 79                     {
 80                         Value = new FileCacheOption
 81                         {
 82                             FilePath = filePath,
 83                             FileSize = fileInfo.Length,
 84                             LastWriteTime = fileInfo.LastWriteTime,
 85                             Encoding = encoding
 86                         },
 87                         ExpiredTime = expiredTime
 88                     };
 89                 }
 90             }
 91         }
 92 
 93         public static T Get<T>(string key)
 94         {
 95             if (string.IsNullOrEmpty(key))
 96                 throw new ArgumentNullException(nameof(key));
 97             var value = default(T);
 98             lock (objLock)
 99             {
100                 var exist = CacheEntryDictionary.ContainsKey(key);
101                 if (!exist) return value;
102                 var obj = CacheEntryDictionary[key];
103                 if (obj == null)
104                 {
105                     CacheEntryDictionary.Remove(key);
106                     return value;
107                 }
108                 if (obj.ExpiredTime == DateTime.MinValue || DateTime.Now.Subtract(obj.ExpiredTime).TotalSeconds >= 0)
109                 {
110                     CacheEntryDictionary.Remove(key);
111                     return value;
112                 }
113                 return (T)Convert.ChangeType(obj.Value, typeof(T));
114             }
115         }
116 
117         public static void Remove(string key)
118         {
119             if (string.IsNullOrEmpty(key))
120                 throw new ArgumentNullException(nameof(key));
121             try
122             {
123                 lock (objLock)
124                 {
125                     var exist = CacheEntryDictionary.ContainsKey(key);
126                     if (!exist) return;
127                     CacheEntryDictionary.Remove(key);
128                     if (FileCacheEntryDictionary.ContainsKey(key))
129                         FileCacheEntryDictionary.Remove(key);
130                 }
131             }
132             catch (Exception e)
133             {
134                 throw e;
135             }
136         }
137 
138         public static void Clear()
139         {
140             try
141             {
142                 lock (objLock)
143                 {
144                     CacheEntryDictionary.Clear();
145                     FileCacheEntryDictionary.Clear();
146                     LastRestoreCachTime = DateTime.Now;
147                 }
148             }
149             catch (Exception e)
150             {
151                 throw e;
152             }
153         }
154 
155         public static int Count => CacheEntryDictionary?.Count ?? 0;
156 
157         private static void RestoreCache()
158         {
159             while (true)
160             {
161                 if (CacheEntryDictionary.Keys.Count < 1) return;
162                 try
163                 {
164                     bool isEnter = false;
165                     Monitor.TryEnter(CacheEntryDictionary, 1000, ref isEnter);
166                     if (isEnter)
167                     {
168                         if (CacheEntryDictionary.Count > RestoreCacheCount && DateTime.Now.Subtract(LastRestoreCachTime).TotalMinutes > 1)
169                         {
170                             var keys = CacheEntryDictionary.Where(m => m.Value.ExpiredTime < DateTime.Now).Select(m => m.Key).ToList();
171                             foreach (var key in keys)
172                             {
173                                 CacheEntryDictionary.Remove(key);
174                             }
175                             LastRestoreCachTime = DateTime.Now;
176                         }
177                     }
178                 }
179                 finally
180                 {
181                     Monitor.Exit(CacheEntryDictionary);
182                 }
183                 184                 Thread.Sleep(1000 * 6);
185             }
186         }
187 
188         private static void ReFreshFileCache()
189         {
190             while (true)
191             {
192                 if (FileCacheEntryDictionary.Keys.Count < 1) return;
193                 try
194                 {
195                     bool isEnter = false;
196                     Monitor.TryEnter(FileCacheEntryDictionary, 100, ref isEnter);
197                     if (isEnter && FileCacheEntryDictionary.Keys.Count > 0)
198                     {
199                         var keys = FileCacheEntryDictionary.Keys.ToList();
200                         foreach (var key in keys)
201                         {
202                             var value = FileCacheEntryDictionary[key];
203                             if (value.ExpiredTime <= DateTime.Now)
204                             {
205                                 FileCacheEntryDictionary.Remove(key);
206                                 continue;
207                             }
208                             var fileCacheOption = value.Value as FileCacheOption;
209                             if (fileCacheOption == null)
210                             {
211                                 FileCacheEntryDictionary.Remove(key);
212                                 continue;
213                             }
214                             if (!File.Exists(fileCacheOption.FilePath))
215                             {
216                                 FileCacheEntryDictionary.Remove(key);
217                                 CacheEntryDictionary.Remove(key);
218                                 continue;
219                             }
220                             FileInfo fileInfo = new FileInfo(fileCacheOption.FilePath);
221                             if (fileInfo.Length != fileCacheOption.FileSize || fileInfo.LastWriteTime != fileCacheOption.LastWriteTime)
222                             {
223                                 fileCacheOption.LastWriteTime = fileInfo.LastWriteTime;
224                                 fileCacheOption.FileSize = fileInfo.Length;
225                                 FileCacheEntryDictionary[key] = new MyCacheEntity { Value = fileCacheOption, ExpiredTime = value.ExpiredTime };
226                                 using (var stream = new StreamReader(fileCacheOption.FilePath, fileCacheOption.Encoding))
227                                 {
228                                     Set(key, stream.ReadToEnd(), (int)value.ExpiredTime.Subtract(DateTime.Now).TotalSeconds);
229                                 }
230                             }
231                         }
232                     }
233                 }
234                 finally
235                 {
236                     Monitor.Exit(FileCacheEntryDictionary);
237                 }
238                
239                 Thread.Sleep(100);
240             }
241         }
242     }
243     public class MyCacheEntity
244     {
245         public object Value { get; set; }
246         public DateTime ExpiredTime { get; set; }
247     }
248 
249     public class FileCacheOption
250     {
251         public string FilePath { get; set; }
252         public long FileSize { get; set; }
253         public DateTime LastWriteTime { get; set; }
254         public Encoding Encoding { get; set; }
255     }

其实上边的文件监控 可以用 FileSystemWatcher 来做文件监控,。只是我做测试的时候,在触发多个事件读取文件内容时,会报文件被占用,然后就是在centos 下,编辑文件后保存时,会同时触发 Created、Changed 两个事件,在windows 下不存在这种情况,可能是我的方法设置有问题吧。

posted @ 2019-08-30 00:06  flyfishing  阅读(855)  评论(0编辑  收藏  举报