带你走进缓存世界(3):缓存原理

上次我们了解了缓存的基本使用技能,也知道为什么要用缓存,但只是单单谈到了缓存的优势的一点:就是缓存避免的重复性的耗时操作,提高系统性能。其实,如果缓存使用不当,会适得其反。为了避免这种情况的发生,我们更适合了解下缓存的原理。虽然缓存不仅仅是指缓存在内存里的数据,但本节还是以内存为主。
        假如说A市有1000万人口,我们要根据某个身份证号码,查出这个人的资料,该如何做呢?
        有两种做法:
        1、把这些数据录入数据库,然后给 身份证 建立唯一索引,然后查询 身份证 = xxx 的用户
        2、遍历所有用户,返回 身份证 = xxx 的数据 
        第一种是我们最常见的办法,当然也是比较现实的做法,但要依赖与某数据库。第二种并没有提到在哪里遍历,或许在内存里,也就是说或许我们已经把所有的资料放在内存里了。你们说哪一种速度快呢?
        速度快与慢主要是看访问量,如果访问量小的话,两个没有区别,如果访问量非常大,已经超越了数据库的承受能力时,第一种或许就不合适了,那第二种呢? 更不合适!因为每一个查询都要遍历1000万个资料(最差情况),可想而知cpu的压力巨大。毕竟数据建立了索引,扫描的数据可能只有1条(聚集索引),时间耗费只是索引树(b+tree)的查找,而第二种却是每个都1000万条。也就是第一种的时间复杂度是O(logx(n)),第二种是O(n)。这就是典型的缓存失败案例。


        如何解决这个问题?
        解决问题自然是从数据的结构入手,数据库之所以快就是采用的树结构(多路平衡树),这是一个数据和存储都比较均衡的结构,实际上还有更快的结构,那就是缓存常用的hashtable(哈希表/散列表)结构,看到hashtable,大家应该立刻会想到key/value键值对,是的,键值对更快,hashtable是一种快速定位的数据结构,他查询数据的时间复杂度是O(1),当然实际上是有所偏差的,因为牵扯到hash值的计算和hash值冲突的问题。.NET的对象之根是Object,Object类有一个GetHashCode方法,所以我们的对象默认都有该方法。此方法返回一个int类型的值,即为该对象的hash值。比如,任意一个string字符串,也具备GetHashCode的方法。我们上节中用到了的缓存类的key都是string,每个string都对应了一个hash值,也就是key的hash值对应value的具体位置。假设我们把一个hashtable想成是一个数组,那么假如我们知道了某个元素的下标,想要获取这个元素是不是可以直接Array[下标],就可以了,所以,在此你完全可以把hash值想象成数组下标,所以我们获取value的时候只要提供key,便可以直接计算出hash值(下标),然后直接获取value。
        下面的图片画的是hashtable的具体存储结构。

        

        数字为数组下标,entry为具体KeyValue对象,箭头是value是指向下一个entry的指针
        可以看出有的地方没有数据,有的地方有多条数据,大部分地方是1条数据,每条数据都对应了一个hash值。
        这种情况说明了几点情况:
        1、一个位置对应了多个entry,这说明不同的key的hash值可能是一个。
        2、有的位置没有entry,这说明key所对应的hash值并非是紧密排列的,会造成一定的空间浪费。
        3、大部分是有一个entry,这说明获取hash值的算法比较合理,使的大部分key的hash值都不同。

Entry是一个结构体,里面包含了hash值,key/Value后面的Entry(即next,主要用于hash冲突)

  1. private struct Entry  
  2. {  
  3.     public int hashCode;  
  4.     public int next;  
  5.     public TKey key;  
  6.     public TValue value;  
  7. }  

GetHashCode的具体算法是GetHashCode方法是虚方法,所以我们在定义类时可以按自己的算法计算hash值。优秀的hash算法是减少冲突,均与分布。下面是.NET4.0 string类的GetHashCode代码:

  1. [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), SecuritySafeCritical]  
  2. public override unsafe int GetHashCode()  
  3. {  
  4.   fixed (char* str = ((char*)this))  
  5.   {  
  6.     char* chPtr = str;  
  7.     int num = 0x15051505;  
  8.     int num2 = num;  
  9.     int* numPtr = (int*)chPtr;  
  10.     for (int i = this.Length; i > 0; i -= 4)  
  11.     {  
  12.       num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];  
  13.       if (i <= 2) break;  
  14.       num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];  
  15.       numPtr += 2;  
  16.     }  
  17.     return (num + (num2 * 0x5d588b65));  
  18.   }  
  19. }  

 大家明白大致原理即可,如果想深究,这里有一篇极好的文章推荐阅读:http://www.cnblogs.com/abatei/archive/2009/06/23/1509790.html

 




另外:在.NetFramework里还有一个常用个的类Dictionary<TKey,TValue>,其和Hashtable相比不仅仅是泛型的支持,其内部算法也略有不同。

下面是Dictionary的Insert和Get方法,可以上面上链接里的hashtable做一下对比:

  1. private void Insert(TKey key, TValue value, bool add)  
  2. {  
  3.     if (key == null)  
  4.     {  
  5.         ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);  
  6.     }  
  7.     if (this.buckets == null)  
  8.     {  
  9.         this.Initialize(0);  
  10.     }  
  11.     int num = this.comparer.GetHashCode(key) & 2147483647;//保证hash值为正直  
  12.     int num2 = num % this.buckets.Length;//保证hash值在容器内  
  13.     //此处循环是先直接定位到hash值下标的Entry和其next entry  
  14.     for (int i = this.buckets[num2]; i >= 0; i = this.entries[i].next)  
  15.     {  
  16.         //如果该元素已存在value且key一致  
  17.         if (this.entries[i].hashCode == num && this.comparer.Equals(this.entries[i].key, key))  
  18.         {  
  19.             //如果是添加则抛出已添加异常  
  20.             if (add)  
  21.             {  
  22.                 ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);  
  23.             }  
  24.             //更新该Entry  
  25.             this.entries[i].value = value;  
  26.             this.version++;  
  27.             return;  
  28.         }  
  29.     }  
  30.     int num3;  
  31.     //这里是插入所用,先判断是否有空闲的Entry(可能之前被remove过的)  
  32.     if (this.freeCount > 0)  
  33.     {  
  34.         //如果有,则直接把freeList最顶部的下标(也就是上前面的一个空闲拿出来用)  
  35.         num3 = this.freeList;  
  36.         this.freeList = this.entries[num3].next;  
  37.         this.freeCount--;  
  38.     }  
  39.     else  
  40.     {  
  41.         if (this.count == this.entries.Length)  
  42.         {  
  43.             this.Resize();  
  44.             num2 = num % this.buckets.Length;  
  45.         }  
  46.         num3 = this.count;  
  47.         this.count++;  
  48.     }  
  49.     //此下标构的Entry赋值新的k/v;  
  50.     this.entries[num3].hashCode = num;  
  51.     this.entries[num3].next = this.buckets[num2];  
  52.     this.entries[num3].key = key;  
  53.     this.entries[num3].value = value;  
  54.     this.buckets[num2] = num3;  
  55.     this.version++;  
  56. }  

  1. public TValue this[TKey key]  
  2. {  
  3.     get  
  4.     {  
  5.         int num = this.FindEntry(key);  
  6.         if (num >= 0)  
  7.         {  
  8.             return this.entries[num].value;  
  9.         }  
  10.         ThrowHelper.ThrowKeyNotFoundException();  
  11.         return default(TValue);  
  12.     }  
  13.     [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]  
  14.     set  
  15.     {  
  16.         this.Insert(key, value, false);  
  17.     }  
  18. }  

  1. private int FindEntry(TKey key)  
  2. {  
  3.     if (key == null)  
  4.     {  
  5.         ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);  
  6.     }  
  7.     if (this.buckets != null)  
  8.     {  
  9.         int num = this.comparer.GetHashCode(key) & 2147483647;  
  10.         for (int i = this.buckets[num % this.buckets.Length]; i >= 0; i = this.entries[i].next)  
  11.         {  
  12.             if (this.entries[i].hashCode == num && this.comparer.Equals(this.entries[i].key, key))  
  13.             {  
  14.                 return i;  
  15.             }  
  16.         }  
  17.     }  
  18.     return -1;  
  19. }  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值