我个人比较注重代码的性能,包括时间和空间,代码的可控度;在做开发的时候偶然发现并实现了这个方式,我个人命名他为“借还模式”。不知道是否有相关的模式,我读书少,嘿嘿。。。
很多时候,我们执行一个方法,必须要有很多的new操作,当这个方法执行次数非常多的时候,这个方法内部new的操作将是超级多的。但是我们又不能手动释放内存,只能等待GC来回收。那么我们就有一个问题,既然不能手动释放,而且反正要new,那么干脆我们就不让GC去回收了,让他持续的存在,这样还能节省内存的空间,不是一举两得吗?由此我想到图书馆的图书实现过程,图书的所有权是图书馆的,借出去的是使用权,用完了必须归还。那么就有两个关键的操作“借”和“还”,“借”和“还”都指的是使用权。
代码实现如下:
1 /// <summary> 对象借还管理器 </summary> 2 public class BRManage : IDisposable 3 { 4 private static BRManage br; 5 private static readonly object ins = new object(); 6 private readonly object globalLock = new object(); 7 8 private const int WARNING_COUNT = 10; 9 private readonly Dictionary<Type, Queue<object>> cachePool = new Dictionary<Type, Queue<object>>(); 10 private readonly Dictionary<Type, List<object>> cacheBak = new Dictionary<Type, List<object>>(); 11 12 private bool isdisposing; 13 14 private BRManage() 15 { 16 } 17 18 /// <summary> 单例 </summary> 19 public static BRManage Ins 20 { 21 get 22 { 23 if (br == null) 24 { 25 lock (ins) 26 { 27 if (br == null) 28 { 29 br = new BRManage(); 30 } 31 } 32 } 33 34 return br; 35 } 36 } 37 38 ///<summary> 借出一个对象 </summary> 39 public T Borrow<T>() where T : class, new() 40 { 41 if (isdisposing) 42 throw new ApplicationException("借还管理器已经释放,不能再使用,请重启系统"); 43 44 var type = typeof (T); 45 46 if (!cacheBak.ContainsKey(type)) 47 { 48 lock (globalLock) 49 { 50 if (!cacheBak.ContainsKey(type)) 51 { 52 cachePool.Add(type, new Queue<object>()); 53 cacheBak.Add(type, new List<object>()); 54 } 55 } 56 } 57 58 var que = cachePool[type]; 59 var list = cacheBak[type]; 60 61 var bak = WARNING_COUNT - que.Count; 62 if (bak > 0) 63 { 64 for (int i = 0; i < bak; i++) 65 { 66 var temp = new T (); 67 list.Add(temp); 68 que.Enqueue(temp); 69 } 70 } 71 72 return que.Dequeue() as T; 73 } 74 75 ///<summary> 返还一个对象 </summary> 76 public void Return<T>(T obj) where T : class 77 { 78 if (isdisposing) 79 throw new ApplicationException("借还管理器已经释放,不能再使用,请重启系统"); 80 81 if (obj == null) 82 throw new ArgumentNullException("obj"); 83 84 var type = obj.GetType(); 85 if (!cachePool.ContainsKey(type)) 86 { 87 throw new ArgumentNullException("obj", "该类型未注册过"); 88 } 89 90 if (cacheBak[type].IndexOf(obj) == -1) 91 { 92 throw new ArgumentException("该对象未被索引,不被借还管理", "obj"); 93 } 94 95 cachePool[type].Enqueue(obj); 96 } 97 98 /// <summary> 释放所有数据 </summary> 99 public void Dispose() 100 { 101 isdisposing = true; 102 103 var list = new List<Type>(cachePool.Keys); 104 for (int i = 0, l = list.Count; i < l; i++) 105 { 106 cachePool[list[i]].Clear(); 107 cachePool[list[i]].TrimExcess(); 108 } 109 cachePool.Clear(); 110 list.Clear(); 111 112 list = new List<Type>(cacheBak.Keys); 113 for (int i = 0, l = list.Count; i < l; i++) 114 { 115 cacheBak[list[i]].Clear(); 116 cacheBak[list[i]].TrimExcess(); 117 } 118 cacheBak.Clear(); 119 list.Clear(); 120 list.TrimExcess(); 121 } 122 }
代码解释:
这里单例是为了方便操作,我不喜欢new,也不喜欢static。new代表着空间减少,static如果单纯的作为计算数据没问题,如果保存数据带来并发问题,避免这些问题要增加复杂度。cacheBak的作用是保存对象的所有权,作用是在Dispose的时候释放列表。当然Dispose这里比较简单,在实际用的时候可能需要调用对象的Close或者Dispose方法。cachePool是一个队列,队列是个好东西,这里用来记录对象的使用权,表示还有那些对象可供借出。这里借出对象是没有选择性的,实际根据业务可以有选择的借出。另外有一个WARNING_COUNT常量,相当于警告库存的概念,让使用权队列始终大于10个长度,这样避免并发操作Dequeue抛出操作异常的问题,当然是一定程度上。
借还模式注意事项:
- Borrow和Return必须成对出现,正所谓“有借有还再借不难”,否则就是人品问题;那么代码就是质量问题,如果不Return的后果可能是内存占用量急剧增长。
- 这里用了泛型方法和泛型约束,要求T必须是class,很显然如果是值类型,按副本传递就失去了意义;并且可以new,因为在Borrow里面需要创建对象。
- 在使用完毕的时候,一般系统或者程序关闭的时候执行Dispose,释放使用权和所有权。
- 如果需要的话,可以在Return或者Dispose的时候可以对对象进行一些操作。
- 对于非托管代码慎用,例如对同一个文件操作的Stream可能会出现不可以意料的异常。
借还模式的扩展:
- 可以增加限制让cacheBak所有权列表的长度,不要让所有权列表一直增长下去,可以限制在某个界限值,如果没有可用使用权对象,线程就Sleep一会。
- 可以对Type类型做限制,增加安全性,可以增加一个Register方法,对能使用的Type进行注册,而不是在Borrow中自动创建
- 如果Return的对象在其他地方还有保留的引用,而我又不想让这些保留的引用使用的时候,可以在Return的时候对这个对象进行一些的操作,防止其他保留的引用继续使用,当然比较麻烦。
使用场景:
- 大量小对象的创建,可以节省内存
- 珍惜资源的创建,嘿嘿,你是否想到了不关闭的Connection对象,我这么用了,目前没问题。
测试结果:
先看测试代码如下
class Program { static void Main(string[] args) { var dt = DateTime.Now; // A 方式 //for (int i = 0; i < 10000000; i++) //{ // var o = new MyClass(); // o.p1 = 123; // o.p2 = o.p1; // o.p3 = 12; // o.p4 = o.p1.ToString(); // o.p5 = Guid.NewGuid(); //} //Console.WriteLine((DateTime.Now-dt).TotalMilliseconds); // B 方式 dt = DateTime.Now; for (int i = 0; i < 10000000; i++) { var o = BRManage.Ins.Borrow<MyClass>(); BRManage.Ins.Return(o); } Console.WriteLine((DateTime.Now - dt).TotalMilliseconds); Console.ReadLine(); } } class MyClass { public int p1 { get; set; } public int p2 { get; set; } public byte p3 { get; set; } public string p4 { get; set; } public Guid p5 { get; set; } }
CPU极值 | 执行时间(ms) | 最高内存占用(m) | |
A方案 | 35% | 4300-4500 | 3.7M |
B方案 | 35% | 4300-4500 | 2.1M |
测试比较片面,实际应用的时候比较复杂,整个系统测试比较麻烦,个人认为总的占用空间会减少,同时性能不会降低。
综述:该模式依然是创建模式,主要功能是能节省一定的空间,例子代码中的性能和直接new的性能是一样的,测试的时候有上下浮动,如果对Borrow和Return进行其他操作的时候多多少少会影响一些性能,大家就自己斟酌吧。欢迎园友拍砖,丢鸡蛋。