YYCache 源码分析(二)
本文分析YYMemoryCache实现原理:
YYMemoryCache是内存缓存,所以存取速度非常快,主要用到两种数据结构的LRU淘汰算法
1.LRU
Cache的容量是有限的,当Cache的空间都被占满后,如果再次发生缓存失效,就必须选择一个缓存块来替换掉.LRU法是依据各块使用的情况, 总是选择那个最长时间未被使用的块替换。这种方法比较好地反映了程序局部性规律
2.LRU主要采用两种数据结构实现
双向链表(Doubly Linked List)
哈希表(Dictionary)
3.对一个Cache的操作无非三种:插入、替换、查找
插入:当Cache未满时,新的数据项只需插到双链表头部即可
替换:当Cache已满时,将新的数据项插到双链表头部,并删除双链表的尾结点即可
查找:每次数据项被查询到时,都将此数据项移动到链表头部
4.分析图(分析源码时可以对照该图)
5.YYMemoryCache.m里的两个分类
链表节点_YYLinkedMapNode:
@interface _YYLinkedMapNode : NSObject {
@package
// 指向前一个节点
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
// 指向后一个节点
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
// 缓存key
id _key;
// 缓存对象
id _value;
// 当前缓存内存开销
NSUInteger _cost;
// 缓存时间
NSTimeInterval _time;
}
@end
链表_YYLinkedMap:
@interface _YYLinkedMap : NSObject {
@package
// 用字典保存所有节点_YYLinkedMapNode (为什么不用oc字典?因为用CFMutableDictionaryRef效率高,毕竟基于c)
CFMutableDictionaryRef _dic;
// 总缓存开销
NSUInteger _totalCost;
// 总缓存数量
NSUInteger _totalCount;
// 链表头节点
_YYLinkedMapNode *_head;
// 链表尾节点
_YYLinkedMapNode *_tail;
// 是否在主线程上,异步释放 _YYLinkedMapNode对象
BOOL _releaseOnMainThread;
// 是否异步释放 _YYLinkedMapNode对象
BOOL _releaseAsynchronously;
}
// 添加节点到链表头节点
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
// 移动当前节点到链表头节点
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;
// 移除链表节点
- (void)removeNode:(_YYLinkedMapNode *)node;
// 移除链表尾节点(如果存在)
- (_YYLinkedMapNode *)removeTailNode;
// 移除所有缓存
- (void)removeAll;
@end
方法插入、替换、查找方法实现:
// 添加节点到链表头节点
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
// 字典保存链表节点node
CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
// 叠加该缓存开销到总内存开销
_totalCost += node->_cost;
// 总缓存数+1
_totalCount++;
if (_head) {
// 存在链表头,取代当前表头
node->_next = _head;
_head->_prev = node;
// 重新赋值链表表头临时变量_head
_head = node;
} else {
// 不存在链表头
_head = _tail = node;
}
}
存在表头情况图形分析(其他情况不用图分析,自己想象吧,呵呵)
// 移动当前节点到链表头节点
- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
// 当前节点已是链表头节点
if (_head == node) return;
if (_tail == node) {
//**如果node是链表尾节点**
// 把node指向的上一个节点赋值给链表尾节点
_tail = node->_prev;
// 把链表尾节点指向的下一个节点赋值nil
_tail->_next = nil;
} else {
//**如果node是非链表尾节点和链表头节点**
// 把node指向的上一个节点赋值給node指向的下一个节点node指向的上一个节点
node->_next->_prev = node->_prev;
// 把node指向的下一个节点赋值给node指向的上一个节点node指向的下一个节点
node->_prev->_next = node->_next;
}
// 把链表头节点赋值给node指向的下一个节点
node->_next = _head;
// 把node指向的上一个节点赋值nil
node->_prev = 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;
if (node->_prev) node->_prev->_next = node->_next;
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;
// 总缓存数-1
_totalCount--;
if (_head == _tail) {
// 清除节点,链表上已无节点了
_head = _tail = nil;
} else {
// 设倒数第二个节点为链表尾节点
_tail = _tail->_prev;
_tail->_next = nil;
}
// 返回完tail后_tail将会释放
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);
}
}
}
YYMemoryCache.m实现分析(如果上面弄清楚,接下来就简单多了),增删改都是调用上面的方法,下面分析查找与添加缓存方法实现
// 查找缓存
- (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) {
//** 有对应缓存 **
// 重新更新缓存时间
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);
}
接下来会分析YYDiskCache实现原理
YYCache 源码分析(二)的更多相关文章
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
- YYCache 源码分析(一)
iOS 开发中总会用到各种缓存,YYCache或许是你最好的选择.性能上有优势,用法也很简单.作者ibireme曾经对比过同类轮子:http://blog.ibireme.com/2015/10/26 ...
- 框架-springmvc源码分析(二)
框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...
- Tomcat源码分析二:先看看Tomcat的整体架构
Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...
- 十、Spring之BeanFactory源码分析(二)
Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...
- Vue源码分析(二) : Vue实例挂载
Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...
- 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>
目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...
- ABP源码分析二:ABP中配置的注册和初始化
一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...
- spring源码分析(二)Aop
创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...
随机推荐
- 数组Api .map()的使用
之前并没有过多的使用过这个Api,在此记录下对其的理解,方便以后多多使用. 首先是对map的说明: var mappedArray = array.map(callback[, thisObject] ...
- actionscript sendToURL请求url,传递http_referer分浏览器统计
IE全版本都不传递referer,但会在header中传递X_FLASH_VERSION,例如:"HTTP_X_FLASH_VERSION":"13,0,0,182&qu ...
- JQuery解析HTML、JSON和XML实例详解
1.HTML 有的时候会将一段HTML片段保存在HTML文件中,在另外的主页面直接读取该HTML文件,然后解析里面的HTML代码片段融入到主页面中. fragment.html文件,其内容: 复制代码 ...
- html5系列.基础知识
兼容性问题 创建一个html5页面 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" ...
- python 编程之计算器
作业: 使用正则表达式和递归实现计算器功能. 实现: 1.实现带括号的计算 2.实现指数.加减乘除求余等功能 一.实例说明: 本实例自己写了个版本,但依旧存在一点bug,例:-2-2等计算问题,故最后 ...
- android 开源项目学习
1.Android团队提供的示例项目 如果不是从学习Android SDK中提供的那些样例代码开始,可能没有更好的方法来掌握在Android这个框架上开发.由Android的核心开发团队提供了15个优 ...
- ExtJS 4 表单
Form Panel表单面板就是普通面板Panel增加了表单处理能力.表单面板可以用在任何需要收集用户提交数据的地方.表单面板可以使用Container Layout提供的最简单的拜访表单控件的方式. ...
- SequoiaDB 架构指南
1 简介 SequoiaDB(巨杉数据库)是一款分布式非关系型文档数据库,可以被用来存取海量非关系型的数据,其底层主要基于分布式,高可用,高性能与动态数据类型设计,与当前主流分布式计算框架 Hadoo ...
- FileUtils
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import ja ...
- TWinControl.SetBounds与TWinControl.UpdateBounds赏析(定义和调用)
先看它们的函数内容: procedure TControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); begin // 虚函数,TWinCon ...