MemoryCache是.Net Framework 4.0开始提供的内存缓存类,使用该类型可以方便的在程序内部缓存数据并对于数据的有效性进行方便的管理,借助该类型可以实现ASP.NET中常用的Cache类的相似功能,并且可以适应更加丰富的使用场景。在使用MemoryCache时常常有各种疑问,数据是怎么组织的?有没有可能用更高效的组织和使用方式?数据超时如何控制?为了知其所以然,本文中对于MemoryCache的原理和实现方式进行了深入分析,同时在分析的过程中学习到了许多业界成熟组件的设计思想,为今后的工作打开了更加开阔的思路
本文面向的是.net 4.5.1的版本,在后续的.net版本中MemoryCache有略微的不同,欢迎补充
文章内容较长,预计阅读时间1小时左右
MemoryCache类继承自ObjectCache抽象类,并且实现了IEnumerable和IDisposable接口。跟ASP.NET常用的Cache类实现了相似的功能,但是MemoryCache更加通用。使用它的时候不必依赖于System.Web类库,并且在同一个进程中可以使用MemoryCache创建多个实例。
在使用MemoryCache的时候通常会有些疑问,这个类到底内部数据是如何组织的?缓存项的超时是如何处理的?它为什么宣传自己是线程安全的?为了回答这些问题,接下来借助对于MemoryCache的内部实现一探究竟。
MemoryCache内部数据结构在MemoryCache类内部,数据的组织方式跟MemoryCacheStore、MemoryCacheKey和MemoryCacheEntry这三个类有关,它们的作用分别是:
MemoryCacheStore:承载数据
MemoryCacheKey:构造检索项
MemoryCacheEntry:缓存内部数据的真实表现形式
其关系大致如下图所示:
从图上可以直观的看出,一个MemoryCache实例对象可以包含多个MemoryCacheStore对象,具体有几个需要取决于程序所在的硬件环境,跟CPU数目有关。在MemoryCache的内部,MemoryCacheStore对象就像一个个的小数据库一样,承载着各种数据。所以,要理解MemoryCache内部的数据结构,就需要先理解MemoryCacheStore的地位和作用。
MemoryCacheStore该类型是MemoryCache内部真正用于承载数据的容器。它直接管理着程序的内存缓存项,既然要承载数据,那么该类型中必然有些属性与数据存储有关。其具体表现是:MemoryCache中有一个类型为HashTable的私有属性_entries,在该属性中存储了它所管理的所有缓存项。
Hashtable _entries = new Hashtable(new MemoryCacheEqualityComparer());当需要去MemoryCache中获取数据的时候,MemoryCache所做的第一步就是寻找存储被查找key的MemoryCacheStore对象,而并非是我们想象中的直接去某个Dictionary类型或者HashTable类型的对象中直接寻找结果。
在MemoryCache中查找MemoryCacheStore的方式也挺有趣,主要的逻辑在MemoryCache的GetStore方法中,源码如下(为了理解方便增加了部分注释):
internal MemoryCacheStore GetStore(MemoryCacheKey cacheKey) { int hashCode = cacheKey.Hash;//获取key有关的hashCode值 if (hashCode < 0) { //避免出现负数 hashCode = (hashCode == Int32.MinValue) ? 0 : -hashCode; } int idx = hashCode & _storeMask; //_storeMask跟CPU的数目一致,通过&进行按位与计算获取到对应的Store //本处代码是.NET 4.5的样子,在.NET Framework 4.7.2版本已经改成了使用%进行取余计算,对于正整数来说实际结果是一样的。 return _storeRefs[idx].Target; }既然可能存在多个MemoryCacheStore对象,那么就需要有一定的规则来决定每个Store中存储的内容。从源码中可以看出,MemoryCache使用的是CPU的核数作为掩码,并利用该掩码和key的hashcode来计算缓存项的归属地,确实是简单而高效。
MemoryCacheKeyMemoryCacheKey的类功能相对比较简单,主要用于封装缓存项的key及相关的常用方法。
上文提到了MemoryCacheStore中_entries的初始化方式,在构造函数的参数是一个MemoryCacheEqualityComparer对象,这是个什么东西,又是起到什么作用的呢?
MemoryCacheEqualityComparer类实现了IEqualityComparer接口,其中便定义了哈希表中判断值相等的方法,来分析下源码:
internal class MemoryCacheEqualityComparer: IEqualityComparer { bool IEqualityComparer.Equals(Object x, Object y) { Dbg.Assert(x != null && x is MemoryCacheKey); Dbg.Assert(y != null && y is MemoryCacheKey); MemoryCacheKey a, b; a = (MemoryCacheKey)x; b = (MemoryCacheKey)y; //MemoryCacheKey的Key属性就是我们在获取和设置缓存时使用的key值 return (String.Compare(a.Key, b.Key, StringComparison.Ordinal) == 0); } int IEqualityComparer.GetHashCode(Object obj) { MemoryCacheKey cacheKey = (MemoryCacheKey) obj; return cacheKey.Hash; } }