我个人比较注重代码的性能,包括时间和空间,代码的可控度;在做开发的时候偶然发现并实现了这个方式,我个人命名他为“借还模式”。不知道是否有相关的模式,我读书少,嘿嘿。。。
很多时候,我们执行一个方法,必须要有很多的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进行其他操作的时候多多少少会影响一些性能,大家就自己斟酌吧。欢迎园友拍砖,丢鸡蛋。