缓存这个东西可大可小,小到一个静态的字段,大到将整个数据库Cache起来。项目开发过程中缓存的应用到处可见,在这篇博文中笔者就来谈谈自己的项目中关于缓存实现。
最常见的缓存功能,如C#语言中的Dictionary对象,应该至少包含以下几个功能:
Init():缓存的初始化;
如:Dictionary<int, object> dic = new Dictioinary<int, object>();
Add():增加缓存;
如:dic.Add(1, new object());
Set():设置缓存 ;
这里的Set()和Add()是有一点区别的,Add()的时候发现已存在的缓存,则直接返回;而调用Set()的时候会检查,如果不存在执行Add();如果存在,则替换(Remove first,then add)。如:dic[1] = new object();
Reload();重新加载缓存,在某些情况想功能相当于Init;
如:dic=new Dictionary<int, object>();
Remove():移除缓存;
如:dic.Remove(1),(注:执行Remove方法的时候,类似于Set,当key不存在的时候,不作任何处理);
Clear():清空缓存。
如:dic.clear();
如果你用过Dictionary对象的话,相信上面提到的方法对你都不会感到陌生,但是,仅仅一个Dictionary就能够满足我们的需要吗?这里笔者来说说Dictionary的缺点:
一个Dictionary对象的实例是不够用的;
并非所有的缓存的key都是int类型,所以你会在需要的地方不断的定义Dictionary对象,管理起来到底有多难,你懂的。
Dictionary的索引问题;
大家都知道,在进行Dictionary[key]操作的时候,首先要判断是否存在。如果存在,则返回【或许你可以通过Dictionary.TryGetValue(...)方法来避开这一步检查】;如果不存在,则要通过一些方式来获取该对象,然后追加到缓存中去;
Dictionary的遍历问题;
或许你会讲,Dictionary是支持foreach遍历的,好吧,这个我也承认;但是,你有没有在有的时候,需要采用for的方式来遍历Dictioanry对象实例呢?那个时候我想你肯定也纠结了一番。
Dictionary的查找问题;
查找?不是可以通过key来索引吗,还需要什么查找?好吧,这个我也承认;但是有些时候,我不仅仅只是需要通过键名key来查找啊,可能还需要通过dic.Find<TValue>(Predicate match), dic.FindAll<TValue>(Predicate match)等方式来查找,这个Dictionary有吗?答案是:没有。
Dictionary的线程安全问题www.it165.net;
这点不多说,因为Dictionary不是线程安全的。
Dictionary的与实际数据的同步问题;
一般来说,既然用到缓存,那么这些数据基本上是读操作远远大于写操作的,所以不可能为了那么一点点的写操作,来额外加多一个定时器进行数据同步,如果是这样的话,如果有100类数据需要进行缓存,那不是伴随着100个定时。,难以想想,这是多么大的一个累赘啊。
请别上火,因为写了这么多还在说理论;在写我自己的缓存实现的时候,我首先要谈谈我这个缓存管理的应用场景,因为任何一项技术都是因为特定需求而产生的,脱离了实际需求的技术都是空谈,说白了就是无用功。
在我的项目中,有很多的基础对象,如果数据字典,枚举,模块,权限等数据库对象,这些信息是在项目开发完成后基本上是不会再发生太大的改变的,而且这些数据的访问平率是非常高的,如果每次访问都需要一次数据库连接的话,那对数据库的压力可想而知了。下面是ConcurrentDictionary.cs的代码:
001.
public
class
ConcurrentDictionary<TKey, TData>
002.
{
003.
#region 私有字段
004.
005.
/// <summary>
006.
/// 单一源数据(GetOneDataByOneKey)获取器
007.
/// </summary>
008.
private
Func<TKey, TData> _SourceDataGetter;
009.
/// <summary>
010.
/// 所有源数据获取器
011.
/// </summary>
012.
private
Func<List<TData>> _SourceAllDataGetter;
013.
/// <summary>
014.
/// 缓存存放字典对象
015.
/// </summary>
016.
private
Dictionary<TKey, TData> _Dic;
017.
/// <summary>
018.
/// 缓存存放列表对象
019.
/// </summary>
020.
private
List<TData> _List;
021.
/// <summary>
022.
/// 缓存锁
023.
/// </summary>
024.
private
object
_Lock;
025.
026.
#endregion
027.
028.
#region 公共属性
029.
030.
/// <summary>
031.
/// 换成对象个数
032.
/// </summary>
033.
public
int
Count
034.
{
035.
get
036.
{
037.
return
this
._Dic.Count;
038.
}
039.
}
040.
041.
/// <summary>
042.
/// 列表数据
043.
/// </summary>
044.
public
List<TData> List
045.
{
046.
get
047.
{
048.
if
(
this
._List.Count <
this
._Dic.Count)
049.
{
050.
this
._List.Clear();
051.
foreach
(KeyValuePair<TKey, TData> kvp
in
this
._Dic)
052.
{
053.
this
._List.Add(kvp.Value);
054.
}
055.
}
056.
return
this
._List;
057.
}
058.
}
059.
060.
#endregion
061.
062.
#region 构造函数
063.
064.
/// <summary>
065.
/// 默认构造
066.
/// </summary>
067.
public
ConcurrentDictionary()
068.
{
069.
this
._Dic =
new
Dictionary<TKey, TData>();
070.
this
._List =
new
List<TData>();
071.
this
._Lock =
new
object
();
072.
}
073.
074.
/// <summary>
075.
/// 设置源数据获取器
076.
/// </summary>
077.
/// <param name="sourceDataGetter">单一源数据(GetOneDataByOneKey)获取器</param>
078.
public
ConcurrentDictionary(Func<TKey, TData> sourceDataGetter)
079.
{
080.
if
(sourceDataGetter ==
null
)
throw
new
ArgumentNullException(
"sourceDataGetter"
);
081.
this
._SourceDataGetter = sourceDataGetter;
082.
this
._Dic =
new
Dictionary<TKey, TData>();
083.
this
._List =
new
List<TData>();
084.
this
._Lock =
new
object
();
085.
}
086.
087.
/// <summary>
088.
/// 设置源数据获取器
089.
/// </summary>
090.
/// <param name="sourceAllDataGetter">所有源数据获取器</param>
091.
public
ConcurrentDictionary(Func<List<TData>> sourceAllDataGetter)
092.
{
093.
if
(sourceAllDataGetter ==
null
)
throw
new
ArgumentNullException(
"sourceAllDataGetter"
);
094.
this
._SourceAllDataGetter = sourceAllDataGetter;
095.
this
._Dic =
new
Dictionary<TKey, TData>();
096.
this
._List = sourceAllDataGetter();
097.
this
._Lock =
new
object
();
098.
}
099.
100.
#endregion
101.
102.
#region 公共方法
103.
104.
/// <summary>
105.
/// 获取缓存数据
106.
/// <para> 如果缓存中不存在,则通过SourceGetter获取</para>
107.
/// <para> 如果通过SourceGetter获取到null对象,则不添加到缓存</para>
108.
/// </summary>
109.
/// <param name="key">键名</param>
110.
/// <param name="value">键值</param>
111.
/// <returns>返回是否获取成功</returns>
112.
public
bool
Get(TKey key,
out
TData value)
113.
{
114.
if
(_Dic.TryGetValue(key,
out
value))
return
true
;
115.
else
116.
{
117.
lock
(_Lock)
118.
{
119.
if
(_Dic.TryGetValue(key,
out
value))
return
true
;
120.
if
(_SourceDataGetter ==
null
)
return
false
;
121.
TData tempData = _SourceDataGetter(key);
122.
if
(tempData !=
null
)
123.
{
124.
_Dic.Add(key, tempData);
125.
_List.Add(tempData);
126.
value = tempData;
127.
return
true
;
128.
}
129.
return
false
;
130.
}
131.
}
132.
}
133.
134.
/// <summary>
135.
/// 设置缓存数据
136.
/// </summary>
137.
/// <param name="key">键名</param>
138.
/// <param name="value">键值</param>
139.
/// <returns>返回是否设置成功,如果键值已存在,则返回false</returns>
140.
public
bool
Add(TKey key, TData value)
141.
{
142.
if
(_Dic.ContainsKey(key))
return
false
;
143.
else
144.
{
145.
lock
(_Lock)
146.
{
147.
if
(_Dic.ContainsKey(key))
return
false
;
148.
else
149.
{
150.
_Dic.Add(key, value);
151.
if
(!
this
._List.Contains(value))
this
._List.Add(value);
152.
return
true
;
153.
}
154.
}
155.
}
156.
}
157.
158.
/// <summary>
159.
/// 设置缓存数据
160.
/// </summary>
161.
/// <param name="key">键名</param>
162.
/// <param name="value">键值</param>
163.
/// <returns>返回是否设置成功,如果键值已存在,则覆盖</returns>
164.
public
bool
Set(TKey key, TData value)
165.
{
166.
if
(_Dic.ContainsKey(key))
167.
{
168.
lock
(
this
._Lock)
169.
{
170.
//移除老数据
171.
TData oldData = _Dic[key];
172.
_List.Remove(oldData);
173.
//增加新数据
174.
_Dic[key] = value;
175.
_List.Add(value);
176.
return
true
;
177.
}
178.
}
179.
else
180.
{
181.
lock
(_Lock)
182.
{
183.
if
(_Dic.ContainsKey(key))
184.
{
185.
//移除老数据
186.
TData oldData = _Dic[key];
187.
_List.Remove(oldData);
188.
//增加新数据
189.
_Dic[key] = value;
190.
_List.Add(value);
191.
return
true
;
192.
}
193.
else
194.
{
195.
_Dic.Add(key, value);
196.
if
(!
this
._List.Contains(value))
this
._List.Add(value);
197.
return
true
;
198.
}
199.
}
200.
}
201.
}
202.
203.
/// <summary>
204.
/// 通过SourceDataGetter重新加载指定key的值
205.
/// </summary>
206.
/// <param name="key">键值</param>
207.
/// <returns></returns>
208.
public
bool
Reload(TKey key)
209.
{
210.
if
(_SourceDataGetter ==
null
)
return
false
;
211.
TData tempData = _SourceDataGetter(key);
212.
return
this
.Set(key, tempData);
213.
}
214.
215.
/// <summary>
216.
/// 通过SourceAllDataGetter重新加载所有缓存对象
217.
/// </summary>
218.
/// <returns></returns>
219.
public
bool
ReloadAll()
220.
{
221.
if
(_SourceAllDataGetter ==
null
)
return
false
;
222.
lock
(
this
._Lock)
223.
{
224.
this
._List = _SourceAllDataGetter();
225.
}
226.
return
true
;
227.
}
228.
229.
/// <summary>
230.
/// 移除键/值
231.
/// </summary>
232.
/// <param name="key">键名</param>
233.
/// <returns>返回是否移除成功,如果不存在,则返回false</returns>
234.
public
bool
Remove(TKey key)
235.
{
236.
TData tempData;
237.
if
(
this
._Dic.TryGetValue(key,
out
tempData))
238.
{
239.
lock
(_Lock)
240.
{
241.
_Dic.Remove(key);
242.
_List.Remove(tempData);
243.
return
true
;
244.
}
245.
}
246.
return
false
;
247.
}
248.
249.
/// <summary>
250.
/// 清空缓存
251.
/// </summary>
252.
public
void
Clear()
253.
{
254.
lock
(_Lock)
255.
{
256.
_Dic.Clear();
257.
_List.Clear();
258.
}
259.
}
260.
261.
#endregion
代码中的注释写的相对比较详细了,所以怎么用这里就不作任何多余的解释了,如果只是上面这个类的话,还不足以满足上面列出的Dictionary的缺点的第一点,所以笔者另外写了一个缓存管理类,如下:
001.
public
static
class
CacheManager
002.
{
003.
#region 私有字段
004.
005.
/// <summary>
006.
/// 缓存器
007.
/// </summary>
008.
static
Dictionary<
int
,
object
> _Dic;
009.
010.
#endregion
011.
012.
#region 私有方法
013.
014.
static
CacheManager()
015.
{
016.
_Dic =
new
Dictionary<
int
,
object
>();
017.
}
018.
/// <summary>
019.
/// 获取下一个缓存键名
020.
/// </summary>
021.
/// <returns></returns>
022.
private
static
int
_GetNextKey()
023.
{
024.
int
key = Common.Random.Next(1, 1000000);
025.
while
(_Dic.ContainsKey(key))
026.
{
027.
key =
new
Random().Next(1, 1000000);
028.
}
029.
return
key;
030.
}
031.
/// <summary>
032.
/// 注册字典
033.
/// </summary>
034.
/// <typeparam name="TKey">缓存键名类型</typeparam>
035.
/// <typeparam name="TData">缓存键值类型</typeparam>
036.
/// <param name="dic">缓存</param>
037.
/// <returns>缓存类别键名</returns>
038.
private
static
int
_Register<TKey, TData>(ConcurrentDictionary<TKey, TData> dic)
039.
{
040.
int
key = _GetNextKey();
041.
_Dic.Add(key, dic);
042.
return
key;
043.
}
044.
045.
#endregion
046.
047.
#region 公共方法
048.
049.
/// <summary>
050.
/// 注册缓存,并返回缓存键值
051.
/// </summary>
052.
/// <typeparam name="TKey">缓存键名类型</typeparam>
053.
/// <typeparam name="TData">缓存键值类型</typeparam>
054.
/// <returns>缓存类别键名</returns>
055.
public
static
int
Register<TKey, TData>()
056.
{
057.
return
_Register(
new
ConcurrentDictionary<TKey, TData>());
058.
}
059.
060.
/// <summary>
061.
/// 注册缓存,并返回缓存键值
062.
/// </summary>
063.
/// <typeparam name="TKey">缓存键名类型</typeparam>
064.
/// <typeparam name="TData">缓存键值类型</typeparam>
065.
/// <param name="sourceDataGetter">单一源数据(GetOneDataByOneKey)获取器</param>
066.
/// <returns>缓存类别键名</returns>
067.
public
static
int
Register<TKey, TData>(Func<TKey, TData> sourceDataGetter)
068.
{
069.
return
_Register(
new
ConcurrentDictionary<TKey, TData>(sourceDataGetter));
070.
}
071.
072.
/// <summary>
073.
/// 注册缓存,并返回缓存键值
074.
/// </summary>
075.
/// <typeparam name="TKey">缓存键名类型</typeparam>
076.
/// <typeparam name="TData">缓存键值类型</typeparam>
077.
/// <param name="sourceAllDataGetter">所有源数据获取器</param>
078.
/// <returns>缓存类别键名</returns>
079.
public
static
int
Register<TKey, TData>(Func<List<TData>> sourceAllDataGetter)
080.
{
081.
return
_Register(
new
ConcurrentDictionary<TKey, TData>(sourceAllDataGetter));
082.
}
083.
084.
/// <summary>
085.
/// 获取缓存数据
086.
/// <para> 如果缓存中不存在,则通过SourceGetter获取</para>
087.
/// <para> 如果通过SourceGetter获取到null对象,则不添加到缓存</para>
088.
/// </summary>
089.
/// <typeparam name="TKey">缓存键名类型</typeparam>
090.
/// <typeparam name="TData">缓存键值类型</typeparam>
091.
/// <param name="cacheTypeKey">缓存类别键名</param>
092.
/// <param name="key">键名</param>
093.
/// <param name="value">键值</param>
094.
/// <returns>返回是否获取成功</returns>
095.
public
static
bool
Get<TKey, TData>(
int
cacheTypeKey, TKey key,
out
TData value)
096.
{
097.
object
obj;
098.
if
(_Dic.TryGetValue(cacheTypeKey,
out
obj))
099.
{
100.
ConcurrentDictionary<TKey, TData> dic = obj
as
ConcurrentDictionary<TKey, TData>;
101.
return
dic.Get(key,
out
value);
102.
}
103.
value =
default
(TData);
104.
return
false
;
105.
}
106.
107.
/// <summary>
108.
/// 设置缓存数据
109.
/// </summary>
110.
/// <typeparam name="TKey">缓存键名类型</typeparam>
111.
/// <typeparam name="TData">缓存键值类型</typeparam>
112.
/// <param name="cacheTypeKey">缓存类别键名</param>
113.
/// <param name="key">键名</param>
114.
/// <param name="value">键值</param>
115.
/// <returns>返回是否设置成功,如果键值已存在,则返回false</returns>
116.
public
static
bool
Add<TKey, TData>(
int
cacheTypeKey, TKey key, TData value)
117.
{
118.
object
obj;
119.
if
(_Dic.TryGetValue(cacheTypeKey,
out
obj))
120.
{
121.
ConcurrentDictionary<TKey, TData> dic = obj
as
ConcurrentDictionary<TKey, TData>;
122.
return
dic.Add(key, value);
123.
}
124.
return
false
;
125.
}
126.
127.
/// <summary>
128.
/// 设置缓存数据
129.
/// </summary>
130.
/// <typeparam name="TKey">缓存键名类型</typeparam>
131.
/// <typeparam name="TData">缓存键值类型</typeparam>
132.
/// <param name="cacheTypeKey">缓存类别键名</param>
133.
/// <param name="key">键名</param>
134.
/// <param name="value">键值</param>
135.
/// <returns>返回是否设置成功,如果键值已存在,则覆盖</returns>
136.
public
static
bool
Set<TKey, TData>(
int
cacheTypeKey, TKey key, TData value)
137.
{
138.
object
obj;
139.
if
(_Dic.TryGetValue(cacheTypeKey,
out
obj))
140.
{
141.
ConcurrentDictionary<TKey, TData> dic = obj
as
ConcurrentDictionary<TKey, TData>;
142.
return
dic.Set(key, value);
143.
}
144.
return
false
;
145.
}
146.
147.
/// <summary>
148.
/// 通过SourceDataGetter重新加载指定key的值
149.
/// </summary>
150.
/// <typeparam name="TKey">缓存键名类型</typeparam>
151.
/// <typeparam name="TData">缓存键值类型</typeparam>
152.
/// <param name="cacheTypeKey">缓存类别键名</param>
153.
/// <param name="key">键值</param>
154.
/// <returns></returns>
155.
public
static
bool
Reload<TKey, TData>(
int
cacheTypeKey, TKey key)
156.
{
157.
object
obj;
158.
if
(_Dic.TryGetValue(cacheTypeKey,
out
obj))
159.
{
160.
ConcurrentDictionary<TKey, TData> dic = obj
as
ConcurrentDictionary<TKey, TData>;
161.
return
dic.Reload(key);
162.
}
163.
return
false
;
164.
}
165.
166.
/// <summary>
167.
/// 通过SourceAllDataGetter重新加载指定类型缓存的所有缓存对象
168.
/// </summary>
169.
/// <typeparam name="TKey">缓存键名类型</typeparam>
170.
/// <typeparam name="TData">缓存键值类型</typeparam>
171.
/// <param name="cacheTypeKey">缓存类别键名</param>
172.
/// <returns></returns>
173.
public
static
bool
Reload<TKey, TData>(
int
cacheTypeKey)
174.
{
175.
object
obj;
176.
if
(_Dic.TryGetValue(cacheTypeKey,
out
obj))
177.
{
178.
ConcurrentDictionary<TKey, TData> dic = obj
as
ConcurrentDictionary<TKey, TData>;
179.
return
dic.ReloadAll();
180.
}
181.
return
false
;
182.
}
183.
184.
/// <summary>
185.
/// 移除键/值
186.
/// </summary>
187.
/// <typeparam name="TKey">缓存键名类型</typeparam>
188.
/// <typeparam name="TData">缓存键值类型</typeparam>
189.
/// <param name="cacheTypeKey">缓存类别键名</param>
190.
/// <param name="key">键名</param>
191.
/// <returns>返回是否移除成功,如果不存在,则返回false</returns>
192.
public
static
bool
Remove<TKey, TData>(
int
cacheTypeKey, TKey key)
193.
{
194.
object
obj;
195.
if
(_Dic.TryGetValue(cacheTypeKey,
out
obj))
196.
{
197.
ConcurrentDictionary<TKey, TData> dic = obj
as
ConcurrentDictionary<TKey, TData>;
198.
return
dic.Remove(key);
199.
}
200.
return
false
;
201.
}
202.
203.
/// <summary>
204.
/// 清空缓存
205.
/// </summary>
206.
/// <typeparam name="TKey">缓存键名类型</typeparam>
207.
/// <typeparam name="TData">缓存键值类型</typeparam>
208.
/// <param name="cacheTypeKey">缓存类别键名</param>
209.
public
static
void
Clear<TKey, TData>(
int
cacheTypeKey)
210.
{
211.
object
obj;
212.
if
(_Dic.TryGetValue(cacheTypeKey,
out
obj))
213.
{
214.
ConcurrentDictionary<TKey, TData> dic = obj
as
ConcurrentDictionary<TKey, TData>;
215.
dic.Clear();
216.
}
217.
}
218.
219.
/// <summary>
220.
/// 清空所有缓存
221.
/// </summary>
222.
public
static
void
ClearAll()
223.
{
224.
_Dic.Clear();
225.
}
226.
227.
#endregion
以上两个类 就是我的缓存管理的全部实现了,谢谢!