【内存篇】为什么选择YYCache

最近走在学习有关移动端内存的路上,这不慢悠悠的走到了YYCache的路标下。那就从自己的角度先说说为什么在项目中选择了YYCache做缓存。

准备

LRU缓存机制是什么?

一般的数据在存储时都会考虑采用栈(先进后出) 还是 队列(先进先出)的模式,举例以队列(先进先出)的模式管理缓存中的数据,那么如下图:


先进先出模式

设置缓存空间只能存储数量为2时,当A\B存储后再需要空间存储C时,那么就只能先移除A数据。换句话说,每次删除的都是优先级低的数据,而现在队列的处理方式相当于 (缓存加入的时间) == (优先级),越先加入的数据优先级会越低,也就会越先被删除。

那么如果 A 在插入 C数据时,被访问了多次,而且在插入C数据之后也访问了多次,按照队列(先进先出)的模式会不会导致 A 不断的移除又不断的加入呢?

确实会这样,那么 删除的优先级 就不单单等同于缓存第一次加入的时间,而是等同于每次刷新的时间,每次调用Get时都不断的刷新缓存数据的时间,也就是LRU(least-recently-used)缓存机制。


LRU缓存机制
基于以上的几个问题就能LRU缓存机制的实现原理:

① A虽然是最先缓存的数据,优先级最低,但是每次访问都能必须将A放置到最高优先级的位置.
② 涉及到数据的多次位置变更,所以链式存储优于线性存储。但是链表在搜索操作时的时间复杂度为0(n),所以必须有辅助工具: hash表 + 双向链表。

用图就能很简单的描述出整体的思路:


LRU缓存机制实现思路

LeetCode上的N.146 LRU缓存机制来验证以上的思路(Python版):
① 结点 Node

class Node: # 构建结点
    def __init__(self):
        self.key = 0
        self.val = 0
        self.prev = None  # 前面结点
        self.next = None  # 后面结点

② 链表管理

class LRUCache:

    def __add(self, node): #添加node
        node.next = self.__header.next
        node.prev = self.__header
        self.__header.next.prev = node
        self.__header.next = node
    def __remove(self, node): # 移除node
        node.prev.next = node.next
        node.next.prev = node.prev

    def __move_to_end(self, node): #移动到前面,也就是将优先级设置最高
        self.__remove(node)
        self.__add(node)
    def __remove_tail(self):
        res = self.__tail.prev
        self.__remove(res)
        return res


    def __init__(self,capacity):
        self.capacity = capacity #缓存容量
        self.dic = {}  # hash
        self.size = 0
        self.__header, self.__tail = Node(), Node()
        self.__header.next = self.__tail
        self.__tail.prev = self.__header


    def get(self, key): # get Node
        node = self.dic.get(key, None)
        if node is not None:
            self.__move_to_end(node)
            return node.val
        return -1
    def put(self,key, val):  # push new Node
        node = self.dic.get(key, None)
        if node is None:
            node = Node()
            node.key = key
            node.val = val
            self.__add(node)
            self.dic[key] = node
            self.size += 1
        else:
            node.val = val
            self.__move_to_end(node)
        if self.size > self.capacity:
            tail = self.__remove_tail()
            self.dic.pop(tail.key)

查找、插入、删除的时间复杂度都为O(1).


Memory Cache和On-disk Cache有什么差别?

Memory Cache就是RAM(运行内存),CPU能直接访问,但是不能掉电存储。
on-Disk Cache 是ROM,不能被CPU直接访问,能掉电存储,以文件File 或 数据库SQlite等形式进行存储。

YYCache

架构
Memory Cache
是一种快速存储键值对的内存缓存。相对于NSDictionary,键会被retained而不会capied。API的性能和NSCache类似,所有的方法都是线程安全
与NSCache不同的地方:
① 使用了LRU缓存机制移除对象
② 能被cost花销、count数量和age缓存时间控制
③ 能配置当接收到内存警告或App进入后台时自动释放对象

从以上对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; // 哈希表直接在map的内部,并没有并行放在外部
    NSUInteger _totalCost;
    NSUInteger _totalCount;
    _YYLinkedMapNode *_head; // 头部指针
    _YYLinkedMapNode *_tail; // 尾部指针
    BOOL _releaseOnMainThread;
    BOOL _releaseAsynchronously;
}

通过CoreFoundation 提高了_dic中的操作性能,在该链表类中也同样包括对插入、删除结点相关的方法:



③ 实际操作 YYMemoryCache

  • 通知的处理
shouldRemoveAllObjectsOnMemoryWarning // 默认为YES,在接收到内存警告时移除所有的对象
shouldRemoveAllObjectsWhenEnteringBackground // 默认为YES,在App进入后台时移除所有的对象
  • 线程的处理
releaseOnMainThread  //默认为NO, 如果为YES就会在主线程中释放,只有当包含了如UIView/CALayer只能在主线程中操作的对象才设置YES
releaseAsynchronously //默认为YES,会异步的释放键值对避免阻塞当前方法

在release释放某个node时,会进入其他线程中让block持有holder,然后由该queue异步释放该holder中的node

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

推荐阅读更多精彩内容