关于全局缓存的一种简单实现方法
缓存,在.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 下不存在这种情况,可能是我的方法设置有问题吧。
尊重他人劳动成功。转载请注明出处! http://www.cnblogs.com/flyfishing/