YYCach 源码小记

  • YYCache中,永远都是先访问内存缓存,然后再访问磁盘缓存(包括了写入,读取,查询,删除缓存的操作)。而且关于内存缓存(_memoryCache)的操作,是不存在block回调的。

  • YYCache 如果不指定存储方式,默认存入内存的时候同时写入磁盘

  • 在读取缓存的操作中,如果在内存缓存中无法获取对应的缓存,则会去磁盘缓存中寻找。如果在磁盘缓存中找到了对应的缓存,则会将该对象再次写入内存缓存中,保证在下一次尝试获取同一缓存时能够在内存中就能返回,提高速度。

  • 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
    双向链表相对与单向链表来说多,在每个节点中多了一个指向前驱的prior指针。这样在删除一个节点时操作会比较方便。

    为什么不选择单向链表:单链表的节点只知道它后面的节点(只有指向后一节点的指针),而不知道前面的。所以如果想移动其中一个节点的话,其前后的节点不好做衔接。

  • YYDiskCache与YYMemoryCache的相同点:
    都具有查询,写入,读取,删除缓存的接口。
    不直接操作缓存,也是间接地通过另一个类(YYKVStorage)来操作缓存。
    它使用LRU算法来清理缓存。
    支持按 cost,count 和 age 这三个维度来清理不符合标准的缓存。

  • LRU淘汰算法:
    LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”;
    新数据插入到链表头部;
    每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
    当链表满的时候,将链表尾部的数据丢弃。

  • 在YYMemoryCache中,使用了双向链表这个数据结构来保存这些缓存:
    当写入一个新的缓存时,要把这个缓存节点放在链表头部,并且并且原链表头部的缓存节点要变成现在链表的第二个缓存节点。
    当访问一个已有的缓存时,要把这个缓存节点移动到链表头部,原位置两侧的缓存要接上,并且原链表头部的缓存节点要变成现在链表的第二个缓存节点。
    (根据清理维度)自动清理缓存时,要从链表的最后端逐个清理。

  • YYMemoryCache不同点是:
    根据缓存数据的大小来采取不同的形式的缓存:
    数据库sqlite: 针对小容量缓存,缓存的data和元数据都保存在数据库里。
    文件+数据库的形式: 针对大容量缓存,缓存的data写在文件系统里,其元数据保存在数据库里。
    除了 cost,count 和 age 三个维度之外,还添加了一个磁盘容量的维度。

  • YYDiskCache 缓存数据的长度大于20kb,就使用文件存储;如果小于这个值,就是用sqlite存储

  • YYKVStorage实例负责保存和管理所有磁盘缓存

  • YYKVStorage 读写缓存模式:
    YYKVStorageTypeFile 文件读取
    YYKVStorageTypeSQLite 数据库读写
    YYKVStorageTypeMixed 根据策略决定使用文件还是数据库读写数据

  • YYKVStorage 缓存:
    都是通过saveItemWithKey: value: filename: extendedData:方法将缓存数据写入硬盘;
    写入时,判断filename是否存在,不存在则把value存入数据库,反之value就不存入;
    无论filename是否存
    在,key、filename、extendedData都会写入数据库;
    当filename为空时,会先在数据库中查找key对应的filename, 如果filename存在,从文件中删除,再存入数据库,不存在则直接存入数据库。

  • YYMemoryCache是利用key-value机制内存缓存类,所有的方法都是线程安全的: 调用增删时,都会用互斥锁pthread_mutex_lock来的保证线程安全。

  • 互斥锁pthread_mutex_lock :
    互斥锁是在多线程程序中同步访问手段是使用互斥量。程序员给某个对象加上一把“锁”,每次只允许一个线程去访问它。如果想对代码关键部分的访问进行控制,你必须在进入这段代码之前锁定一把互斥量,在完成操作之后再打开它。

@implementation YYMemoryCache {
   // 声明互斥锁
   pthread_mutex_t _lock;
   .....
}
- (instancetype)init {
   self = super.init;
   //初始化互斥锁
   pthread_mutex_init(&_lock, NULL);
   ......
   return self;
}

- (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread {
   //加琐
   pthread_mutex_lock(&_lock);

   _lru->_releaseOnMainThread = releaseOnMainThread;

   //解琐
   pthread_mutex_unlock(&_lock);
}

- (void)dealloc{
   //释放该锁的数据结构
   pthread_mutex_destroy(& _lock);  
}

注意点:
1、互斥量需要时间来加锁和解锁。锁住较少互斥量的程序通常运行得更快。所以,互斥量应该尽量少,够用即可,每个互斥量保护的区域应则尽量大。
2、互斥量的本质是串行执行。如果很多线程需要领繁地加锁同一个互斥量,
则线程的大部分时间就会在等待,这对性能是有害的。如果互斥量保护的数据(或代码)包含彼此无关的片段,则可以特大的互斥量分解为几个小的互斥量来提高性能。这样,任意时刻需要小互斥量的线程减少,线程等待时间就会减少。所以,互斥量应该足够多(到有意义的地步),每个互斥量保护的区域则应尽量的少。

为什么使用互斥锁:
互斥锁缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务,但对于内存缓存的存取来说,它非常合适。

  • YYDiskCache 则选择了更适合它的 dispatch_semaphore 来保证线程安全:
    dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源,YYDiskCache在写入比较大的缓存时,可能会有比较长的等待时间,所以采用dispatch_semaphore

  • 内存警告和进入后台的监听
    YYCache默认在收到内存警告和进入后台时,自动清除所有内存缓存,清除缓存默认在子线程中进行。

  • static inline 内联函数:
    修饰函数,可以当作一个宏定义调用
    inline内联函数的说明:
    1.内联函数只是我们向编译器提供的申请,编译器不一定采取inline形式调用函数
    2.内联函数不能承载大量的代码.如果内联函数的函数体过大,编译器会自动放弃内联
    3.内联函数的定义须在调用之前

优点相比于函数:
inline函数避免了普通函数的,在汇编时必须调用call的缺点:取消了函数的参数压栈,减少了调用的开销,提高效率.所以执行速度确比一般函数的执行速度要快,集成了宏的优点,使用时直接用代码替换(像宏一样);

优点相比于宏:
1、避免了宏的缺点:需要预编译.因为inline内联函数也是函数,不需要预编译.
2、编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
3、可以使用所在类的保护成员及私有成员。

inline函数注意事项:
1、你可以使用inline函数完全取代表达式形式的宏定义。
2、内联函数一般只会用在函数内容非常简单的时候,这是因为,内联函数的代码会在任何调用它的地方展开,如果函数太复杂,代码膨胀带来的恶果很可能会大于效率的提高带来的益处。
3、在内联函数内不允许用循环语句和 开关语句。如果内联函数有这些语句,则编译将该函数视同普通函数那样产生函数调用代码,递归函数(自己调用自己的函数)是不能被用来做内联函数的。内联 函数只适合于只有1~5行的小函数。对一个含有许多语句的大函数,函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现,函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

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

推荐阅读更多精彩内容

  • 从 YYCache 源码 Get 到如何设计一个优秀的缓存 来源:Lision 前言 iOS 开发中总会用到各种缓...
    今天lgw阅读 6,030评论 1 22
  • YYCache简介 YYCache由YYMemoryCache(高速内存缓存)和YYDiskCache(低速磁盘缓...
    简书lu阅读 1,454评论 0 5
  • 作者设计思路 1.YYMemoryCache YYMemoryCache负责管理内存缓存。这个类是线程安全的。 L...
    WeiHing阅读 650评论 0 7
  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子。 除根结点和叶子结点外,其它每个结点至少有m...
    文档随手记阅读 13,232评论 0 25
  • 1 泳裤已买 2尿不湿基本热卖花王不要拿楼上去 3 洁的款今天发的没给他制单 4 货很多发了自己厂里。 5 bio...
    陆凤雀阅读 111评论 0 0