LRU算法与YYMemoryCache

LRU是一种常见的页面置换算法,在计算中,所有的文件操作都要放在内存中进行,然而计算机内存大小是固定的,所以我们不可能把所有的文件都加载到内存,因此我们需要制定一种策略对加入到内存中的文件进行选择。

LRU原理

LRU的设计原理就是,当数据在最近一段时间经常被访问,那么它在以后也会经常被访问。这就意味着,如果经常访问的数据,我们需要然其能够快速命中,而不常访问的数据,我们在容量超出限制内,要将其淘汰。

LRU的实现方式有很多种,一般比较常见的就是双向链表+字典(哈希表)的方式,在iOS端实现该方式的就有 YYMemoryCache
YYMemoryCache目前提供的功能有如下:

/** 缓存名称. */
@property (nullable, copy) NSString *name;

/** 对象数 */
@property (readonly) NSUInteger totalCount;
/**
最大对象数
 */
@property NSUInteger countLimit;

/**
缓存中是否包含某个key
 */
- (BOOL)containsObjectForKey:(id)key;

/**
将缓存的对象返回
 */
- (nullable id)objectForKey:(id)key;

/**
将某对象添加进缓存
 */
- (void)setObject:(nullable id)object forKey:(id)key;
/**
移除某个对象
 */
- (void)removeObjectForKey:(id)key;

/**
清空缓存
 */
- (void)removeAllObjects;

/**
超出最大数后,删除超出部分缓存
 */
- (void)trimToCount:(NSUInteger)count;

YYMemoryCache其底层是使用双向链表+字典的方式来实现的;

_YYLinkedMapNode 为双向链表的节点
_YYLinkedMap是管理双向链表以及实现LRU算法的核心

_YYLinkedMapNode其结构如下
@interface _YYLinkedMapNode : NSObject {
    @package
    __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
    __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
    id _key;
    id _value;
    NSUInteger _cost;
    NSTimeInterval _time;
}
@end
_YYLinkedMap其结构如下
@interface _YYLinkedMap : NSObject {
    @package
    CFMutableDictionaryRef _dic; // do not set object directly
    NSUInteger _totalCost;
    NSUInteger _totalCount;
    _YYLinkedMapNode *_head; // MRU, do not change it directly
    _YYLinkedMapNode *_tail; // LRU, do not change it directly
    BOOL _releaseOnMainThread;
    BOOL _releaseAsynchronously;
}
//在头部插入
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;

//将某个节点取出重置到头部,LRU算法的核心,当有某个节点二次被使用的时候,将该节点的位置提前放到head处
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;

//删除某个节点
- (void)removeNode:(_YYLinkedMapNode *)node;

//从尾部删除节点,也可以认为是淘汰某个节点
- (_YYLinkedMapNode *)removeTailNode;

//清空
- (void)removeAll;

下面是模仿YYMemoryCache用swift双链表+字典来做的实现:


class FMMemmoryCache:NSObject{
    
    var lruMap:FMDoubleLinkWithLRU = FMDoubleLinkWithLRU<Any>()
    
    //缓存最大数
    var limitCount:Int = 5
    //当前缓存数
    var currentLimit:Int {
        get{
            return lruMap.dic.count
        }
    }
    
    override init(){
        
    }
    //是否包含某个key
    func containsObjectForKey(_ key:String) -> Bool{
        return lruMap.dic.keys.contains(key)
    }
    //返回在缓存中,key所对应的具体值
    func objectForKey(_ key:String) -> Any?{
        return lruMap.dic[key]?.val
    }
    //向缓存中添加某个对象,并设置key值
    func append(_ obj:Any,key:String){
       
        if self.containsObjectForKey(key){
            let node = lruMap.dic[key]!
            lruMap.bringNodeToHead(node)
        }else{
            let node = FMDoubleLinkNode<Any>.init(key, obj)
            lruMap.insertNodeAtHead(node)
        }
        
        self.trimToCount()
        
    }
    //移除缓存中的某个对象
    func removeObjectForKey(_ key:String){
        if lruMap.dic.keys.contains(key){
            lruMap.removeNode(lruMap.dic[key]!)
        }
    }
    //超出最大限制数量,进行删减
    func trimToCount(){
        while lruMap.dic.count > self.limitCount{
            lruMap.removeTailNode()
        }
    }
    
    
}


class FMDoubleLinkNode<T>:NSObject{
    var val:T?
    var key:String
    weak var preNode:FMDoubleLinkNode? = nil
    weak var nextNode:FMDoubleLinkNode? = nil
    init(_ key:String,_ val:T?) {
        self.key = key
        self.val = val
    }
    
}

class FMDoubleLinkWithLRU<T>:NSObject{
    var dic:[String:FMDoubleLinkNode<T>] = [String:FMDoubleLinkNode<T>]()
    //这里采用虚拟的头结点
    var head:FMDoubleLinkNode<T> = FMDoubleLinkNode<T>.init("", nil)
    var tail:FMDoubleLinkNode<T> = FMDoubleLinkNode<T>.init("", nil)

//    //当前缓存数
//    var currentLimit:Int = 0
    
    override init() {
        self.head.nextNode = tail
        self.tail.preNode = self.head
    }
    
}
extension FMDoubleLinkWithLRU{
    
    func insertNodeAtHead(_ node:FMDoubleLinkNode<T>){
        node.preNode = self.head
        node.nextNode = self.head.nextNode
        node.nextNode?.preNode = node
        self.head.nextNode = node
        self.dic[node.key] = node
        
    }
    func bringNodeToHead(_ node:FMDoubleLinkNode<T>){
        if self.dic.count < 2 {
            return
        }
        node.preNode?.nextNode = node.nextNode
        node.nextNode?.preNode = node.preNode
        self.insertNodeAtHead(node)
    }
    func removeNode(_ node:FMDoubleLinkNode<T>){
        if self.dic.count < 1 {
            return
        }
        node.preNode?.nextNode = node.nextNode
        node.nextNode?.preNode = node.preNode
        
        self.dic.removeValue(forKey: node.key)
    }
    func removeTailNode(){
        if self.dic.count < 1 {
            return
        }
        if let key = self.tail.preNode?.key{
            self.dic.removeValue(forKey: key)
            self.tail.preNode = self.tail.preNode?.preNode
            self.tail.preNode?.nextNode = self.tail
        }
    }
    func removeAll(){
        if self.dic.count < 1 {
            return
        }
        self.head.nextNode = self.tail
        self.tail.preNode = self.head
        self.dic.removeAll()
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容