美文网首页
YYCach 源码小记

YYCach 源码小记

作者: 不知蜕变的挣扎 | 来源:发表于2018-04-18 14:43 被阅读28次
    • 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行的小函数。对一个含有许多语句的大函数,函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现,函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

    相关文章

      网友评论

          本文标题:YYCach 源码小记

          本文链接:https://www.haomeiwen.com/subject/zubvkftx.html