首页投稿iOS基础知识

【内存篇】为什么选择YYCache

2020-03-21  本文已影响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
        });
    }
上一篇 下一篇

猜你喜欢

热点阅读