C#之文件缓存
写在开头
今天就放假了,照理说应该写今年的总结了,但是回头一看,很久没有写过技术类的文字了,还是先不吐槽了。
关于文件缓存
写了很多的代码,常常在写EXE(定时任务)或者写小站点(数据的使用和客户端调用之间)都需要用到缓存,数据在内存和文本都保留一个整体。
当然也可以写到数据库,不过个人觉得不方便查看和管理。(数据量不大)
第一个版本
一般来说,就是一个缓存类,然后存放缓存文件路径,真实数据集合,读写集合,读写文件。
先提供一个公用类
1 public class TestInfo 2 { 3 public string Name { get; set; } 4 5 public int Age { get; set; } 6 7 public string Value { get; set; } 8 }
然后是第一个对文件读写操作的缓存了
1 public class Cache_Test_1 2 { 3 private static List<TestInfo> dataList = new List<TestInfo>(); 4 5 private static readonly string cachePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache", "test.txt"); 6 7 private static readonly string SEP_STR = "---"; 8 9 static Cache_Test_1() 10 { 11 string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache"); 12 if (!Directory.Exists(dir)) 13 { 14 Directory.CreateDirectory(dir); 15 } 16 17 if (File.Exists(cachePath)) 18 { 19 string[] lines = File.ReadAllLines(cachePath, Encoding.UTF8); 20 foreach (var line in lines) 21 { 22 string[] lineArray = line.Split(new string[] { SEP_STR }, StringSplitOptions.None); 23 if (lineArray.Length == 3) 24 { 25 dataList.Add(new TestInfo() 26 { 27 Name = lineArray[0], 28 Age = int.Parse(lineArray[1]), 29 Value = lineArray[2] 30 }); 31 } 32 } 33 } 34 } 35 36 public static void AddInfo(TestInfo info) 37 { 38 lock (dataList) 39 { 40 var item = dataList.Find(p => p.Name == info.Name); 41 if (item == null) 42 { 43 dataList.Add(info); 44 } 45 else 46 { 47 item.Age = info.Age; 48 item.Value = info.Value; 49 } 50 51 WriteFile(); 52 } 53 } 54 55 public static TestInfo GetInfo(string name) 56 { 57 lock (dataList) 58 { 59 return dataList.Find(p => p.Name == name); 60 } 61 } 62 63 public static List<TestInfo> GetAll() 64 { 65 lock (dataList) 66 { 67 return dataList; 68 } 69 } 70 71 private static void WriteFile() 72 { 73 StringBuilder content = new StringBuilder(); 74 foreach (var item in dataList) 75 { 76 content.AppendLine(item.Name + SEP_STR + item.Age + SEP_STR + item.Value); 77 } 78 79 File.WriteAllText(cachePath, content.ToString(), Encoding.UTF8); 80 } 81 }
但是,这样的操作如果多了起来,问题就出来了。每次写一种缓存就要写一个缓存操作的类了,写着写着,这样的体力活就有点累了,于是
穷则思变,就想,写一个通用一点的吧。于是又了第二个版本
第二个版本
这个版本的目的就是解决重复劳动,见代码
1 public class Cache_Test_2<T> where T : new() 2 { 3 /// <summary> 4 /// 缓存分隔符 5 /// </summary> 6 private static readonly string SEP_STR = "---"; 7 8 /// <summary> 9 /// 缓存临时对象集合 10 /// </summary> 11 private static List<T> dataList = new List<T>(); 12 13 /// <summary> 14 /// 缓存文本路径 15 /// </summary> 16 private static string cachePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache", typeof(T).Name.ToString() + ".txt"); 17 18 static Cache_Test_2() 19 { 20 string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache"); 21 if (!Directory.Exists(dir)) 22 { 23 Directory.CreateDirectory(dir); 24 } 25 26 if (File.Exists(cachePath)) 27 { 28 Type t = typeof(T); 29 string[] lines = File.ReadAllLines(cachePath, Encoding.UTF8); 30 foreach (var line in lines) 31 { 32 string[] lineArray = line.Split(new string[] { SEP_STR }, StringSplitOptions.None); 33 if (line.Contains(SEP_STR)) 34 { 35 List<PropertyIndexInfo> list = new List<PropertyIndexInfo>(); 36 T model = new T(); 37 PropertyInfo[] ps = t.GetProperties(); 38 for (int i = 0; i < lineArray.Length; i++) 39 { 40 var p = ps[i]; 41 if (p.PropertyType == typeof(int)) 42 { 43 p.SetValue(model, Convert.ToInt32(lineArray[i]), null); 44 } 45 else if (p.PropertyType == typeof(string)) 46 { 47 p.SetValue(model, lineArray[i], null); 48 } 49 } 50 51 dataList.Add(model); 52 } 53 } 54 } 55 } 56 57 /// <summary> 58 /// 新增一个缓存 59 /// </summary> 60 /// <param name="t"></param> 61 public static void Add(T t) 62 { 63 lock (dataList) 64 { 65 dataList.Add(t); 66 67 WriteFile(); 68 } 69 } 70 71 /// <summary> 72 /// 读取缓存集合 73 /// </summary> 74 /// <returns></returns> 75 public static List<T> GetAll() 76 { 77 lock (dataList) 78 { 79 return dataList; 80 } 81 } 82 83 /// <summary> 84 /// 写入缓存文件(全量) 85 /// </summary> 86 private static void WriteFile() 87 { 88 StringBuilder content = new StringBuilder(); 89 foreach (var item in dataList) 90 { 91 List<string> list = new List<string>(); 92 var ps = typeof(T).GetProperties(); 93 foreach (var p in ps) 94 { 95 object p_object = p.GetValue(item, null); 96 string value = p_object.ToString(); 97 list.Add(value); 98 } 99 100 content.AppendLine(string.Join(SEP_STR, list.ToArray())); 101 } 102 103 File.WriteAllText(cachePath, content.ToString(), Encoding.UTF8); 104 } 105 }
虽然,第二个版本出来了,但是大多数时候,我们创建缓存都是在已有的类上面进行操作,不然每次创建缓存可能就需要一个CacheModel这样一个对象了,
这样还有,并不是所有的字段我们都是需要进入缓存文件,这样的情况该如何操作呢,于是我们再次优化了一下代码,出现了目前来说的第三个版本了。
第三个版本
这里,就会新增几个类了,为了解决第二个版本不能解决的问题,当然具体使用还是要看业务场景,因为,更通用就代表更依赖配置了。(代码类)
这里需要几个基本类,用来保存临时管理的。
1 /// <summary> 2 /// 特性:指定属性的排序和是否出现在缓存中使用 3 /// </summary> 4 public class CacheOrderAttribute : Attribute 5 { 6 public int Index { get; set; } 7 } 8 9 /// <summary> 10 /// 对字符串分割的结果的值进行排序 11 /// </summary> 12 public class CacheIndexInfo 13 { 14 public int Index { get; set; } 15 16 public string Value { get; set; } 17 } 18 19 /// <summary> 20 /// 对字段的属性进行排序 21 /// </summary> 22 public class PropertyIndexInfo 23 { 24 public int Index { get; set; } 25 26 public PropertyInfo PropertyInfo { get; set; } 27 }
有了特性,我们就能对单个类的属性进行特性筛查,排序,最终得到我们需要的和不需要的,最开始改好了,楼主测试了一下,速度上还可以,但是数据了一多,
这种每次全部写入文件的方式就low了,于是改成了Append的方式,大大提升了速度。同时添加了一个类,对具体分割的值进行调整的。代码如下:
1 /// <summary> 2 /// 文件缓存共有类 3 /// </summary> 4 /// <typeparam name="T">类</typeparam> 5 public class File_Common_Cache<T> where T : new() 6 { 7 /// <summary> 8 /// 缓存分隔符 9 /// </summary> 10 private static string SEP_STR = "---"; 11 12 /// <summary> 13 /// 缓存临时对象集合 14 /// </summary> 15 private static List<T> dataList = new List<T>(); 16 17 /// <summary> 18 /// 缓存文本路径 19 /// </summary> 20 private static string cachePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache", typeof(T).Name.ToString() + ".txt"); 21 22 static File_Common_Cache() 23 { 24 string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache"); 25 if (!Directory.Exists(dir)) 26 { 27 Directory.CreateDirectory(dir); 28 } 29 30 if (File.Exists(cachePath)) 31 { 32 Type t = typeof(T); 33 string[] lines = File.ReadAllLines(cachePath, Encoding.UTF8); 34 foreach (var line in lines) 35 { 36 string[] lineArray = line.Split(new string[] { SEP_STR }, StringSplitOptions.None); 37 if (line.Contains(SEP_STR)) 38 { 39 List<PropertyIndexInfo> list = new List<PropertyIndexInfo>(); 40 T model = new T(); 41 PropertyInfo[] ps = t.GetProperties(); 42 foreach (var p in ps) 43 { 44 var ads = p.GetCustomAttributesData(); 45 if (ads.Count > 0) 46 { 47 int index = Convert.ToInt32(ads[0].NamedArguments[0].TypedValue.Value); 48 list.Add(new PropertyIndexInfo() { Index = index, PropertyInfo = p }); 49 } 50 } 51 52 list = list.OrderBy(p => p.Index).ToList(); 53 for (int i = 0; i < list.Count; i++) 54 { 55 var pt = list[i].PropertyInfo.PropertyType; 56 if (pt == typeof(int)) 57 { 58 list[i].PropertyInfo.SetValue(model, Convert.ToInt32(lineArray[i]), null); 59 } 60 else if (pt == typeof(string)) 61 { 62 list[i].PropertyInfo.SetValue(model, lineArray[i], null); 63 } 64 else if (pt == typeof(DateTime)) 65 { 66 list[i].PropertyInfo.SetValue(model, Convert.ToDateTime(lineArray[i]), null); 67 } 68 else 69 { 70 try 71 { 72 list[i].PropertyInfo.SetValue(model, (object)lineArray[i], null); 73 } 74 catch 75 { 76 throw new Exception("不支持属性类型(仅支持,int,string,DateTime,object)"); 77 } 78 } 79 } 80 81 dataList.Add(model); 82 } 83 } 84 } 85 } 86 87 /// <summary> 88 /// 初始化配置(修改默认分割和保存文件使用) 89 /// </summary> 90 /// <param name="sep_str">分隔符</param> 91 /// <param name="fileName">缓存文件名</param> 92 public static void InitSet(string sep_str, string fileName) 93 { 94 SEP_STR = sep_str; 95 cachePath = fileName; 96 } 97 98 /// <summary> 99 /// 新增一个缓存 100 /// </summary> 101 /// <param name="t"></param> 102 public static void Add(T t) 103 { 104 lock (dataList) 105 { 106 dataList.Add(t); 107 108 AppendFile(t); 109 } 110 } 111 112 /// <summary> 113 /// 移除一个缓存 114 /// </summary> 115 /// <param name="t"></param> 116 public static void Remove(T t) 117 { 118 119 } 120 121 /// <summary> 122 /// 读取缓存集合 123 /// </summary> 124 /// <returns></returns> 125 public static List<T> GetAll() 126 { 127 lock (dataList) 128 { 129 return dataList; 130 } 131 } 132 133 /// <summary> 134 /// 写入缓存文件(全量) 135 /// </summary> 136 private static void WriteFile() 137 { 138 StringBuilder content = new StringBuilder(); 139 foreach (var item in dataList) 140 { 141 List<CacheIndexInfo> list = new List<CacheIndexInfo>(); 142 var ps = typeof(T).GetProperties(); 143 foreach (var p in ps) 144 { 145 var ads = p.GetCustomAttributesData(); 146 if (ads.Count > 0) 147 { 148 int index = Convert.ToInt32(ads[0].NamedArguments[0].TypedValue.Value); 149 object p_object = p.GetValue(item, null); 150 string value = string.Empty; 151 if (p.PropertyType == typeof(DateTime)) 152 { 153 value = p_object == null ? DateTime.Parse("1900-1-1").ToString("yyyy-MM-dd HH:mm:ss") : 154 Convert.ToDateTime(p_object).ToString("yyyy-MM-dd HH:mm:ss"); 155 } 156 else 157 { 158 value = p_object == null ? "" : p_object.ToString(); 159 } 160 161 list.Add(new CacheIndexInfo() { Index = index, Value = value }); 162 } 163 } 164 165 list = list.OrderBy(a => a.Index).ToList(); 166 content.AppendLine(string.Join(SEP_STR, (from f in list select f.Value).ToArray())); 167 } 168 169 File.WriteAllText(cachePath, content.ToString(), Encoding.UTF8); 170 } 171 172 /// <summary> 173 /// 写入缓存文件(附加) 174 /// </summary> 175 /// <param name="t"></param> 176 private static void AppendFile(T t) 177 { 178 StringBuilder content = new StringBuilder(); 179 List<CacheIndexInfo> list = new List<CacheIndexInfo>(); 180 var ps = typeof(T).GetProperties(); 181 foreach (var p in ps) 182 { 183 var ads = p.GetCustomAttributesData(); 184 if (ads.Count > 0) 185 { 186 int index = Convert.ToInt32(ads[0].NamedArguments[0].TypedValue.Value); 187 object p_object = p.GetValue(t, null); 188 string value = string.Empty; 189 if (p.PropertyType == typeof(DateTime)) 190 { 191 value = p_object == null ? DateTime.Parse("1900-1-1").ToString("yyyy-MM-dd HH:mm:ss") : 192 Convert.ToDateTime(p_object).ToString("yyyy-MM-dd HH:mm:ss"); 193 } 194 else 195 { 196 value = p_object == null ? "" : p_object.ToString(); 197 } 198 199 list.Add(new CacheIndexInfo() { Index = index, Value = value }); 200 } 201 } 202 203 list = list.OrderBy(a => a.Index).ToList(); 204 content.AppendLine(string.Join(SEP_STR, (from f in list select f.Value).ToArray())); 205 File.AppendAllText(cachePath, content.ToString(), Encoding.UTF8); 206 } 207 }
测试代码
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 TInfo info = new TInfo(); 6 info.Name = "test"; 7 info.Age = 10; 8 info.Test = "我是测试字符串"; 9 var list = File_Common_Cache<TInfo>.GetAll(); 10 DateTime startTime = DateTime.Now; 11 for (int i = 0; i < 1000; i++) 12 { 13 File_Common_Cache<TInfo>.Add(info); 14 } 15 16 TimeSpan span = DateTime.Now - startTime; 17 Console.WriteLine(span.TotalMilliseconds); 18 Console.ReadLine(); 19 } 20 } 21 22 public class TInfo 23 { 24 [CacheOrder(Index = 0)] 25 public string Name { get; set; } 26 27 [CacheOrder(Index = 2)] 28 public int Age { get; set; } 29 30 [CacheOrder(Index = 1)] 31 public string Test { get; set; } 32 }
测试结果:1秒不到,还是可以。
总结
没啥好总结的,但是不这样写,感觉不规范。写代码的过程中,总是会遇到,写着写着就变成体力活的代码,这个时候,我们就应该认识到问题了,把体力活改变一下,就不再是体力活。
让代码更简单,让生活个多彩。
不足之处,望包含,拍砖,丢鸡蛋。