美文网首页首页投稿iOS基础知识
【内存篇】为什么选择YYCache

【内存篇】为什么选择YYCache

作者: 李周 | 来源:发表于2020-03-21 01:23 被阅读0次
最近走在学习有关移动端内存的路上,这不慢悠悠的走到了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
        });
    }

相关文章

  • 【内存篇】为什么选择YYCache

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

  • YYCache源码总结

    YYCache简介 YYCache由YYMemoryCache(高速内存缓存)和YYDiskCache(低速磁盘缓...

  • iOS源码解析—YYCache(YYMemoryCache)

    概述 上一篇主要讲解了YYCache的文件结构,分析了YYCache类的相关方法,本章主要分析内存缓存类YYMem...

  • iOS开发小贴:缓存

    YYCache学习篇 首先,YYCache GitHub地址 YYCache源码分析(一)YYCache源码分析(...

  • YYCache 源码学习总结

    YYCache 内存分为内存缓存和磁盘缓存 内存缓存 1.内存缓存实现通过CFMutableDictionaryR...

  • YYMemoryCache源码分析

    YYCache 缓存框架 YYMemoryCache 内存中的缓存 YYDiskCache YYKVStorage...

  • 深入理解YYCache

    前言 本篇文章将带来YYCache的解读,YYCache支持内存和本地两种方式的数据存储。我们先抛出两个问题: Y...

  • 源码探索 - YYCache(YYMemoryCache)

    YYMemoryCache是YYCache中的内存缓存模块,是采用了LRU算法实现的经典源码。YYMemoryCa...

  • 单链表基本操作

    早前读YYCache源码时候 ,写过一篇文章 : YYCache源码解读,对源码中使用的LRU算法无比钦佩,其中涉...

  • YYKIT-YYCache

    一、YYCache的组成 YYCache由YYCache、YYDiskCache、YYMemoryCache和YY...

网友评论

    本文标题:【内存篇】为什么选择YYCache

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