设计一个Cache系统

简介:

     今天去面试,面试官让我设计一个cache系统,要求保证最近使用的数据不能被移除出cache,也就是每次添加一个cache项的时候,把最旧的cache项移除出去。

    我只记得操作系统里貌似有个差不多的cache算法,记不起名字来,更别提数据结构了。一开始我执着于用一种数据结构来实现,可是每说出一种,都被面试官指出这种方式的不足。最后终于开窍了,想出了 哈希表+双向链表 的方法。当时也不知道到底对不对,感觉这种实现还过的去吧,查询和增加cache项复杂度都是O(1)。

    回来后一查,原来就是LRU(Least Recent Used)算法。好到事实上就是采用了哈希表+双向链表的组合拳法。文末附上一篇别人写的博文。

   另外,我发现Java1.4中有一个LinkedHashMap,就是哈希表+双链表,有一个removeEldestEntry方法,里面没有实现,override这个方法后很容易实现FIFO的Cache,对其它几个方法也override后想必实现LRU也不难。

 http://blog.sina.com.cn/s/blog_567842410100nf1g.html

Cache(高速缓存), 一个在计算机中几乎随时接触的概念。CPU中Cache能极大提高存取数据和指令的时间,让整个存储器(Cache+内存)既有Cache的高速度,又能有内存的大容量;操作系统中的内存page中使用的Cache能使得频繁读取的内存磁盘文件较少的被置换出内存,从而提高访问速度;数据库中数据查询也用到Cache来提高效率;即便是Powerbuilder的DataWindow数据处理也用到了Cache的类似设计。Cache的算法设计常见的有FIFO(first in first out)和LRU(least recently used),FIFO对CPU的指令序列非常有效,但更多的,对于Memory或者磁盘文件的这种Cache,LRU就更有效多了。FIFO的算法设计很简单明了,就不做讨论,在此只是对LRU展开。

Cache中的存储空间被分成若干块,每一块对应内存或者磁盘文件的一段数据(当然也可以是指令数据),形成这种映射关系,当Cache中的存储块被用完,而需要把新的数据Load进Cache的时候,我们就需要设计一种良好的算法来完成数据块的替换。LRU的思想是基于“最近用到的数据被重用的概率比较早用到的大的多”这个设计规则来实现的。Cache中的所有块位置都用双向链表链接起来,当一个位置被命中后,就将通过调整链表的指向将该位置调整到链表的头位置,新加入的内容直接放在链表的头上。这样,在进行过多次查找操作后,最近被命中过的内容就向链表的头移动,而没有被命中的内容就向链表的后面移动。当需要替换时,链表最后的位置就是最近最少被命中的位置,我们只需要将新的内容放在链表前面,淘汰链表最后的位置就是想了。

对于双向链表的使用,基于两个考虑。首先是Cache中块的命中可能是随机的,和Load进来的顺序无关,所以我们需要用链表这种结构来保存位置队列,使得其可以灵活的调整相互间的次序。其次,双向链表使得在知道一个位置的情况下可以很迅速的移到其他的地方,时间复杂度为O(1)。

查找一个链表中元素的时间复杂度是O(n),每次命中的时候,我们就需要花费O(n)的时间来进行查找,如果不添加其他的数据结构,这个就是我们能实现的最高效率了。目前看来,整个算法的瓶颈就是在查找这里了,怎么样才能提高查找的效率呢?Hash表,对,就是它,数据结构中之所以有它,就是因为它的查找时间复杂度是O(1)。梳理一下思路:对于Cache的每个位置,我们设计一个数据结构来储存Cache块的内容,并实现一个双向链表,其中属性next和prev时双向链表的两个指针,key用于存储对象的键值,value用户存储要cache块对象本身,然后用Hash表来查找具体被命中的Cache块。剩下的就是写Code的事了:我们使用一个hashmap作为cache,用hashmap的检索机制来实现cache查找;并用head和last两个属性来记录链表的头和尾。并提供putEntry(),getEntry()方法来操作该cache。




本文转自 dogegg250 51CTO博客,原文链接:http://blog.51cto.com/jianshusoft/666653,如需转载请自行联系原作者

相关文章
|
4月前
|
存储 缓存 算法
缓存优化利器:5分钟实现 LRU Cache,从原理到代码!
嗨,大家好!我是你们的技术小伙伴——小米。今天带大家深入了解并手写一个实用的LRU Cache(最近最少使用缓存)。LRU Cache是一种高效的数据淘汰策略,在内存有限的情况下特别有用。本文将从原理讲起,带你一步步用Java实现一个简单的LRU Cache,并探讨其在真实场景中的应用与优化方案,如线程安全、缓存持久化等。无论你是初学者还是有一定经验的开发者,都能从中受益。让我们一起动手,探索LRU Cache的魅力吧!别忘了点赞、转发和收藏哦~
117 2
|
6月前
|
缓存 索引
cpu缓存一致性问题---cache写策略
cpu缓存一致性问题---cache写策略
61 1
|
7月前
|
存储 缓存 中间件
中间件Read-Through Cache(直读缓存)策略
【5月更文挑战第7天】中间件Read-Through Cache(直读缓存)策略
68 4
中间件Read-Through Cache(直读缓存)策略
|
7月前
|
缓存 中间件 数据库
中间件Write-Through Cache(直写缓存)策略
【5月更文挑战第7天】中间件Write-Through Cache(直写缓存)策略
114 4
中间件Write-Through Cache(直写缓存)策略
|
7月前
|
存储 缓存 中间件
中间件Read-Through Cache(直读缓存)策略工作原理
【5月更文挑战第11天】中间件Read-Through Cache(直读缓存)策略工作原理
82 3
|
缓存 Unix Linux
记一次探索内存cache优化之旅
本文先介绍文件的LINUX 内存和 page cache 机制,并介绍应用程序级的管理方法,最后介绍针对 应用的内存优化实践。
2574 17
记一次探索内存cache优化之旅
|
缓存 Linux 数据安全/隐私保护
实战分享|Write Cache设置效果为何有差异?
sdparm和hdparm去修改HDD的write cache,发现在系统下write cache设置的效果有差异。
|
内存技术
cache基础
cache
135 0
|
存储 缓存
cache_t结构分析
紧接着我们来分析类结构体中cache_t, 只从单词来看就能猜出来是与缓存有关. 下面我们先看cache_t的源码:
167 0
cache_t结构分析
|
缓存 监控 NoSQL
如何优雅的设计和使用缓存?
1.确认是否需要缓存 在使用缓存之前,需要确认你的项目是否真的需要缓存。使用缓存会引入的一定的技术复杂度,后文也将会一一介绍这些复杂度。一般来说从两个方面来个是否需要使用缓存: CPU占用:如果你有某些应用需要消耗大量的cpu去计算,比如正则表达式,如果你使用正则表达式比较频繁,而其又占用了很多CPU的话,那你就应该使用缓存将正则表达式的结果给缓存下来。
1536 0