YYMemoryCache 是用来存储占用内存大小,速度较快的高速内存缓存,主要用到双向链表和NSDictionary实现了LRU淘汰算法,使用pthread_mutex来保证线程安全。 LRU(最近最少使用):是一种缓存淘汰算法,将最近使用的条目存放到靠近缓存顶部的位置,当一个新条目被访问时,LRU将它放置到缓存的顶部,当缓存达到极限时,较早之前访问的条目将从缓存底部开始被移除。对一个cache对象操作包括插入,移动操作,当在内存中找不到新增对象时,如果内存未满,插入到双链表头部即可,如果内存满,需要删除双链表尾部再来插入到双链表头部,当内存中有这个对象时,只需把这个对象移动到双链表头部即可。 在YYMemeryCache中当发生内存警告或缓存值达到上限时,会优先淘汰那些时间戳靠前的对象,最近常使用的不会被淘汰。
YYMemoryCache类的大体结构如下: YYMemoryCache {
pthread_mutex_t _lock;
_YYLinkedMap *_lru;
dispatch_queue_t _queue; }
_YYLinkedMapNode : NSObject {
**@package**
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic //前驱指针
**__unsafe_unretained** _YYLinkedMapNode *_next; // retained by dic//后继指针
//数据域
id _key; // 存储对象的key
id _value; // 具体存储的对象
NSUInteger _cost; //所存对象占用空间
NSTimeInterval _time; //最近一次使用对象的时间戳 }
_YYLinkedMap : NSObject {
**@package**
CFMutableDictionaryRef _dic; // do not set object directly 用字典保存所有节点
NSUInteger _totalCost;//总缓存开销
NSUInteger _totalCount; //上限容量
_YYLinkedMapNode *_head; // MRU, do not change it directly //首元节点
_YYLinkedMapNode *_tail; // LRU, do not change it directly //尾节点
BOOL _releaseOnMainThread;//是否在主线程上,异步释放_YYLinkedMapNode对象
BOOL _releaseAsynchronously //是否异步释放_YYLinkedMapNode对象
}
//没命中这个对象时,也就是内存中没这个对象 插入到双链表头部
-(void)insertNodeAtHead:(_YYLinkedMapNode *)node;
//内存中存在这个对象 命中了 移动到双链表头部 变成最近最多使用
-(void)bringNodeToHead:(_YYLinkedMapNode *)node;
//删除某个节点
-(void)removeNode:(_YYLinkedMapNode *)node;
// 删除尾节点(如果存在尾部节点 ) -(_YYLinkedMapNode *)removeTailNode;
// 移除所有缓存
-(void)removeAll;
//没命中这个对象时,也就是内存中没这个对象 插入到双链表头部
-(void)insertNodeAtHead:(_YYLinkedMapNode *)node {//新增
CFDictionarySetValue(_dic, ( __bridge const void *)(node->_key), ( __bridge const void *)(node));
_totalCost += node->_cost;//叠加当前对象的缓存到总混存中
_totalCount++;//总缓存数+1
if (_head) {
node->_next = _head;//新增节点的后继指针指向当前的头部节点,以免丢失节点
_head->_prev = node;//当前头部节点前驱指针指向新增节点 上下两部取代当前头节点
_head = node;//重新赋值把新增节点作为头节点
} else {
_head = _tail = node; //双链表不存在链表头和尾
} }
//内存中存在这个对象 命中了 移动到双链表头部 变成最近最多使用 -(void)bringNodeToHead:(_YYLinkedMapNode *)node { if (_head == node) return; //该头节点无需再移动,最好的情况
if* (_tail == node) {//该节点是尾节点
_tail = node->_prev; //把该节点的上个节点变成尾节点
_tail->_next = nil;//由于尾节点的后继指针指向nil,所以要要赋值为nil
} else { //不是尾节点(是头部节点和尾部节点之间的节点)要把该节点上下两个节点链接起来
node->_next->_prev = node->_prev;//把该节点的下个节点的前驱指针指向该节点的上个节点
node->_prev->_next = node->_next;//把该节点的上个节点的后驱指针指向该节点的下个节点
}
node->_next = _head;//把该节点的后驱指针指向当前的头节点
node->_prev = nil;//把该节点的前驱指针指向nil由于是无虚拟头节点上链表(链表的头节点的前驱指针指向nil)
_head->_prev = node;//把当前头节点的前驱指针指向该节点,链接起来
_head = node;//更换头节点
}
// 移除节点 -(void)removeNode:(_YYLinkedMapNode *)node {
// 从字典中移除node
CFDictionaryRemoveValue(_dic, ( __bridge const void *)(node->_key));
//总内存消耗
_totalCost -= node->_cost;
//总缓存数-1 _totalCount--;
if (node->_next) node->_next->_prev = node->_prev;//该节点不是尾节点,把该节点的下个节点的前驱指针指向该节点的上个节点有可能是头节点 刚好指向nil
if (node->_prev) node->_prev->_next = node->_next;//该节点不是头节点,把该节点的上个节点的后继指针指向该节点的下个节点 该节点可能是尾节点 刚好指向nil
if (_head == node) _head = node->_next; // 该节点刚好是头节点 替换头节点
if (_tail == node) _tail = node->_prev;//该节点刚好是尾节点 替换尾节点
}
-(_YYLinkedMapNode *)removeTailNode {
if (!_tail) return nil; //不存在尾节点
_YYLinkedMapNode *tail = _tail; //拷贝一份要删除的尾节点指针
//删除链表尾节点
CFDictionaryRemoveValue(_dic, ( __bridge const void *)(_tail->_key));
_totalCost -= _tail->_cost;//减掉总内存消耗
_totalCount--;//总缓存数减1
if (_head == _tail) {//只有一个节点
_head = _tail = nil;
} else {
_tail = _tail->_prev;//重新赋值尾节点
_tail->_next = nil;//新尾节点的后驱指针指向nil
}
return tail;
}
-(void)removeAll {
//清空内存消耗与缓存数量
_totalCost = 0;
_totalCount = 0;
//清空头尾节点
_head = nil;
_tail = nil;
if (CFDictionaryGetCount(_dic) > 0) {
//拷贝一份字典
CFMutableDictionaryRef holder = _dic;
//重新分配内存空间
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (_releaseAsynchronously) {
//异步释放缓存
dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
CFRelease(holder); // hold and release in specified queue
});
} else if (_releaseOnMainThread && !pthread_main_np()) {
//主线程释放缓存
dispatch_async(dispatch_get_main_queue(), ^{ CFRelease(holder); // hold and release in specified queue });
} else {
//同步释放缓存
CFRelease(holder);
} }
// 查找缓存
- (id)objectForKey:(id)key {
if (!key) return nil;
// 加锁,防止资源竞争
// OSSpinLock 自旋锁,性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务。对于内存缓存的存取来说,它非常合适。
pthread_mutex_lock(&_lock);
// _lru为链表_YYLinkedMap,全部节点存在_lru->_dic中
// 获取节点 _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *) (key));
if (node) {//命中了 时间复杂度O(1)
//** 有对应缓存 **
//重新更新缓存时间
node->_time = CACurrentMediaTime();
// 把当前node移到链表表头(为什么移到表头?根据LRU淘汰算法:Cache的容量是有限的,当Cache的空间都被占满后,如果再次发生缓存失效,就必须选择一个缓存块来替换掉.LRU法是依据各块使用的情况, 总是选择那个最长时间未被使用的块替换。这种方法比较好地反映了程序局部性规律)
[_lru bringNodeToHead:node];//把当前缓存变成最近最多使用 移动到双链表头部
}
// 解锁
pthread_mutex_unlock(&_lock);
// 有缓存则返回缓存值 return node ? node->_value : nil;
}
// 添加缓存
-(void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
if (!key) return;
if (!object) {
// ** 缓存对象为空,移除缓存 **
[self removeObjectForKey:key];
return; }
// 加锁 pthread_mutex_lock(&_lock);
// 查找缓存
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
// 当前时间
NSTimeInterval now = CACurrentMediaTime(); if (node) {//命中
//** 之前有缓存,更新旧缓存 **
// 更新值
_lru->_totalCost -= node->_cost;
_lru->_totalCost += cost;
node->_cost = cost;
node->_time = now;
node->_value = object;
// 移动节点到链表表头
[_lru bringNodeToHead:node];
} else {
//**未命中 之前未有缓存,添加新缓存 **\
// 新建节点
node = [_YYLinkedMapNode new];
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
// 添加节点到表头
[_lru insertNodeAtHead:node];
}
if (_lru->_totalCost > _costLimit) {//达到上限容量
// ** 总缓存开销大于设定的开销 **\
// 异步清理最久未使用的缓存
dispatch_async(_queue, ^{ [self trimToCost:_costLimit];
}); }
if (_lru->_totalCount > _countLimit) {
// ** 总缓存数量大于设定的数量 ** // 移除链表尾节点(最久未访问的缓存)
_YYLinkedMapNode *node = [_lru removeTailNode];//删除尾节点
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{ [node class]; and release in queue
}); } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
pthread_mutex_unlock(&_lock);
}