本文分析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 源码分析(二)的更多相关文章

  1. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  2. YYCache 源码分析(一)

    iOS 开发中总会用到各种缓存,YYCache或许是你最好的选择.性能上有优势,用法也很简单.作者ibireme曾经对比过同类轮子:http://blog.ibireme.com/2015/10/26 ...

  3. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  4. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

  5. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  6. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  7. 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>

    目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

随机推荐

  1. Apache配置域名

    Apache配置域名 在WIN下安装APACHE配置虚拟目录和UNIN下基本是一样的就是修改httpd.conf1:单个IP对应单个域名例如:www.phpunion.com对应192.168.1.1 ...

  2. QTP的DataTable操作整理(注---不知转载多少遍)

    返回值:数字 示例: 以下示例使用 GetRowCount 方法查找 MySheet 运行时数据表中最长的列中的总行数,并将其写入报告. rowcount = DataTable.GetSheet(& ...

  3. 再说CSS3渐变——线性渐变

    渐变背景一直以来在Web页面中都是一种常见的视觉元素.但一直以来,Web设计师都是通过图形软件设计这些渐变效果,然后以图片形式或者背景图片的形式运用到页面中.Web页面上实现的效果,仅从页面的视觉效果 ...

  4. Python3.X与urllib

    在Python3.X中使用urllib时,不能像Python2.X一样直接使用: import urllib response = urllib.urlopen("http://www.ba ...

  5. vs2013update4 vs-mda-remote cordova真机测试ios 解决里面一个坑

    sudo npm install -g vs-mda-remote --user=你的用户名 此步骤为安装vs-mda-remote,如果安装成功 执行vs-mda-remote –secure fa ...

  6. ios中关于delegate(委托)的使用心得

    ios中关于delegate(委托)的使用心得 分类: iOS开发2012-05-15 10:54 34793人阅读 评论(9) 收藏 举报 iosuiviewtimerinterfaceprinti ...

  7. BZOJ 3106 棋盘游戏

    Description 一个\(n \times n(n \le 2)\)棋盘上有黑白棋子各一枚.游戏者A和B轮流移动棋子,A先走. A的移动规则:只能移动白棋子.可以往上下左右四个方向之一移动一格. ...

  8. libevent带负载均衡的多线程使用示例

    功能: 主线程根据负载工作线程负载均衡算法,每隔一秒钟向特定的工作线程发送一条字符串信息,工作线程简单的把字符串信息打开出来.   Makefile   eventtest : eventtest.c ...

  9. WallsEveryDay 必应桌面壁纸

    软件名:WallsEveryday 陈述: 无聊时写着玩的一个桌面壁纸的软件,壁纸是自动从必应下载,所以每天都会有新的. 在ubuntu上测试通过,windows上找了台win7测试通过,其他没测试. ...

  10. splay模板

    点操作: splay树可以一个一个的插入结点,这样的splay树是有序树,结点权值大于左儿子小于右儿子 这样就是点操作 区间操作: 还有就是可以自己建树,这样的splay树就不是按权值的有序树,它不满 ...