关于全局缓存的一种简单实现方法
緩存,在.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 下不存在這種情況,可能是我的方法設置有問題吧。
轉載于:https://www.cnblogs.com/flyfishing/articles/Global_CacheHelper.html
總結
以上是生活随笔為你收集整理的关于全局缓存的一种简单实现方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 问题 1076: 内部收益率
- 下一篇: 魔兽大脚插件怎么用大脚怀旧服插件使用教程