计算机为了提升访问数据的速度,实现了CPU的多级缓存机制,cpu计算时经常要用到的数据会被从其他地方读到cache里,但cache的大小是有限的,在缓存空间不够用时,就势必要逐出数据为新数据腾出空间... 由此引入了LRU策略.
一、实现内存缓存的思路
LRU(Least Recently use), 在空间不够时,将最近最少使用的数据删除来为新数据腾空间,为了实现这样的功能,需要思考的东西🤔:
- 一份一份的数据需要按照时间排列,内存不足时从最老的一份开始删除,直到满足条件。这里令最新数据放在最前面,最老的数据放到最后面。
- 新数据读到内存时,需要插入到最前面;老数据再次被访问时,移动到最前面。
- 访问数据效率要快, 复杂度为O(1).
- 数据的写入、更改需要加锁。
综上所述,应该使用双向链表
来实现这些数据的排列,使得在移动数据到头部时,时间复杂度O(1)。如果是单向链表,则在移动节点时,该节点的上一节点的next指向下一节点时, 上一节点不好访问。
同时为了使得在访问数据时复杂度O(1), 我们还需要使用哈希表
,我们在iOS中,使用字典。NSDictionary和NSSet的底层实现原理
二、具体实现代码
YYCache框架中的YYMemoryCache类就是根据LRU这个策略来实现的,具体代码可以参考之。
关键在于双向链表的节点设置
、双向链表的设计
、缓存类的实现
三部分对应了三个类_YYLinkedMapNode 、_YYLinkedMap、YYMemoryCache.
// 一、双向链表的节点设置
@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
// 二、双向链表的设计
@implementation _YYLinkedMap
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
_totalCost += node->_cost;
_totalCount++;
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;
} else {
node->_next->_prev = node->_prev;
node->_prev->_next = node->_next;
}
node->_next = _head;
node->_prev = nil;
_head->_prev = node;
_head = node;
}
- (_YYLinkedMapNode *)removeTailNode {
if (!_tail) return nil;
_YYLinkedMapNode *tail = _tail;
CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
_totalCost -= _tail->_cost;
_totalCount--;
if (_head == _tail) {
_head = _tail = nil;
} else {
_tail = _tail->_prev;
_tail->_next = nil;
}
return tail;
}
@end
// 三、缓存类的实现
@implementation YYMemoryCache
- (id)objectForKey:(id)key {
if (!key) return nil;
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
node->_time = CACurrentMediaTime();
[_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]; //hold 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);
}
@end
在iOS中系统提供了NSCache这个类来实现内存缓存,为什么选择YYCaChe?
- It uses LRU (least-recently-used) to remove objects; NSCache's eviction method is non-deterministic.
- It can be controlled by cost, count and age; NSCache's limits are imprecise.
- It can be configured to automatically evict objects when receive memory
warning or app enter background.
网友评论