YYCache源码分析(一) - YYMemoryCache

article/2025/10/4 4:14:53

一. 文件结构:

YYCache主要分为YYCache、YYDiskCache和YYMemoryCache三个类,YYDiskCache实现了硬盘缓存的功能,YYMemoryCache实现了内存缓存的功能,YYCache类提供了通用的缓存存取的方法,内部调用YYMemoryCache和YYDiskCache的方法。

二. YYCache的使用:

    // 0.初始化YYCacheYYCache *cache = [YYCache cacheWithName:@"mydb"];// 1.缓存普通字符[cache setObject:@"哈哈哈" forKey:@"name"];NSString *name = (NSString *)[cache objectForKey:@"name"];NSLog(@"name: %@", name);// 2.缓存模型[cache setObject:(id<NSCoding>)model forKey:@"user"];// 3.缓存数组NSMutableArray *array = @[].mutableCopy;for (NSInteger i = 0; i < 10; i ++) {[array addObject:model];}// 异步缓存[cache setObject:array forKey:@"user" withBlock:^{// 异步回调NSLog(@"%@", [NSThread currentThread]);NSLog(@"array缓存完成....");}];// 延时读取dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{// 异步读取[cache objectForKey:@"user" withBlock:^(NSString * _Nonnull key, id<NSCoding>  _Nonnull object) {// 异步回调NSLog(@"%@", [NSThread currentThread]);NSLog(@"%@", object);}];});

三. YYMemoryCache.h文件分析

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// 缓存keyid _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 {// 字典保存链表节点nodeCFDictionarySetValue(_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指向的上一个节点赋值nilnode->_prev = nil;// 把节点赋值给链表头节点的指向的上一个节点_head->_prev = node;_head = node;
}

如果node是非链表尾节点和链表头节点情况图形分析

这里写图片描述

// 移除节点
- (void)removeNode:(_YYLinkedMapNode *)node {// 从字典中移除nodeCFDictionaryRemoveValue(_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;// 加锁,防止资源竞争// 互斥锁。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);
}

YYMemoryCache分析先到这里,接下来还会继续理解和阅读YY大神的更多源码。进行YYMemoryCache源码分析时,也看了很多人的关于YYMemoryCache源码分析的分享,感谢大哥们的分享。


http://chatgpt.dhexx.cn/article/yC4Oaf5F.shtml

相关文章

cache(二)——net.sf.ehcache.CacheManager

EhCacheManagerFactoryBean通过调用 CacheManager类的静态方法来获取实例&#xff0c;那么先看一下CacheManager类提供的static创建方法吧。 使用ehcache.jar中默认的缓存配置文件来创建EhcahceManager对象,调用无参的静态方法&#xff1a; public static void main(String[] …

@CacheEvict-缓存

CacheEvict 【清除缓存】 用于update\insert\delete方法上&#xff0c;清除Cacheable修饰的方法获取的缓存信息 【转载&#xff1a;https://xie.infoq.cn/article/001e0f5ab65fa7dd1484c51e5】 Cacheable 作用缓存&#xff0c;步骤&#xff1a; 注&#xff1a;这里使用 Cacheab…

YYCache源码分析

YYCache是OC用于缓存的第三方框架。 YYCache:同时实现内存缓存和磁盘缓存&#xff0c;且是线程安全的。YYDiskCache:实现磁盘缓存&#xff0c;所有的API是线程安卓的&#xff0c;内部也采用了LRU淘汰算法&#xff0c;主要是SQLite和文件存储两种方式。YYKVStorage:实现磁盘缓存…

Guava Cache、LoadingCache本地缓存的使用

前言&#xff1a;由于用户系统重构存在新老版本,网关对部分数据进行路径写死&#xff0c;所以为了避免频繁请求接口对网关日志记录进行本地缓存处理,学习过程中有Cache和LoadingCache两种模式&#xff0c;一番测试下都可以正常使用&#xff0c;特此记录 准备工作 添加依赖 &l…

YYCache,TMCache,SDImageDiskCache的比较以及二级缓存分析

YYCache传送门 点击打开链接 内存缓存 通常一个缓存是由内存缓存和磁盘缓存组成&#xff0c;内存缓存提供容量小但高速的存取功能&#xff0c;磁盘缓存提供大容量但低速的持久化存储。相对于磁盘缓存来说&#xff0c;内存缓存的设计要更简单些&#xff0c;下面是我调查的一些…

EVCache

EVCache介绍 EVCache是一个开源、快速的分布式缓存&#xff0c;是基于Memcached的内存存储和Spymemcached客户端实现的&#xff0c;是Netflix&#xff08;网飞&#xff09;公司开发的 E&#xff1a;Ephemeral&#xff1a;数据存储是短暂的&#xff0c;有自身的存活时间V&#…

cache 缓存

缓存原理 测试样例 验证码 获取验证码 验证 验证码是否正确 idea 启动缓存 手机验证码 idea 手机 获得验证码 ehchace 数据淘汰策略 使用redis 然后启动 redis 服务器 redis-server.exe redis.windows.conf 启动redis 客户端redis-cli.exe time-to-live 最大活动时间 缓…

YYCache源码解读 (一)

YYCache YYCache是 ibireme 大神在2015年设计的一个IOS缓存工具。   通常来讲&#xff0c;一个缓存包括内存缓存和磁盘缓存。YYCache在磁盘缓存的设计上使用数据库sqlite映射和文件系统映射的方式进行存储&#xff0c;内存中设计了一个双向链表的结构存储数据结点。内存和磁盘…

Guava Cache本地缓存

目录 本地缓存 回顾 Guava Cache介绍 Guava Cache使用 创建 删除 Guava Cache底层实现 本地缓存与分布式缓存对比 缓存三大问题 本地缓存 实现&#xff1a;CurrentHashMap、Guava Cache 缓存在应用服务器&#xff0c;全局变量&#xff0c;JVM缓存 回顾 JVM内存 方…

缓存(ehcache/guavaCache使用)

单机缓存 ehcache&#xff1a;单独使用&#xff1a;与spring集成&#xff1a;编程式操作&#xff1a;注解式使用&#xff1a; 与springboot集成&#xff1a; guava cache&#xff1a;单独使用&#xff1a;spring/springboot集成&#xff1a;自定义KeyGenerator&#xff1a; 自定…

iOS 缓存框架YYCache学习

文章目录 前言一、YYCache的来源二、YYCache的结构1. YYMemoryCache1.1 最近最少使用—LRU(Least Frequently Used)1.2 基于LRU的增删改查1.2.1 增加数据1.2.2 删除数据1.2.3 查找修改数据1.2.4 YYMemoryCache的增删改查 2.YYDiskCache 总结参考文章 前言 提示&#xff1a;这篇…

深入理解YYCache缓存策略

文章目录 前言几个主要成员类1 YYCache2 YYMemoryCache3 YYDiskCache 实例化1 实例方法2 构造器方法 查1 检查是否有缓存2 读缓存 增1 写内存缓存2 写磁盘缓存 删1 清空内存缓存2 清空磁盘缓存 YYMemoryCache 初始化做了什么总结本文完 前言 YYCache是著名iOS框架YYKit的一个组…

Unity OnDestroy 调用

Test Code private GameObject _temp;_temp Instantiate(Resources.Load("gameObject original"), parent) as GameObject;if (Input.GetMouseButtonDown(1)) {Destroy(_temp);_temp null; }试验结果&#xff08;场景中本来存在的gameObjec或Resources出来的game…

报错原因高的地图调用mapView.onDestroy() 崩溃问题

以前在android app中使用地图的项目需要使用高德地图。 按照高德地图的开发文档创建测试项目&#xff0c;导入依赖&#xff0c;很快就成功显示了地图&#xff0c;然后在退出地图Activity时&#xff0c;app立即崩溃&#xff0c;通过追踪&#xff0c;发现是在销毁地图时出现崩溃…

Android跨进程通信Client Crash后Server端onDestroy

hi&#xff0c;粉丝朋友大家好&#xff01; 好久没有给大家写blog了&#xff0c;哈哈&#xff0c;这里说声抱歉&#xff01;实在家里比较忙&#xff0c;今天就来给大家分享一个跨进程专题课中学员问的一个问题&#xff0c;blog就来解答一下这个问题。 问题背景&#xff1a; 视频…

Activity onDestroy延迟回调

前端时间工作的时候遇到了两个奇怪的问题&#xff1a; 使用百度步行导航的时候&#xff0c;开启导航后立即退出&#xff0c;再次进入的时候就会黑屏&#xff1b;使用度小满支付的时候&#xff0c;当支付成功后页面一直显示loading&#xff0c;过了10s左右才恢复正常。 这看似…

基于MFC的OpenDDS发布订阅例子(PubSubDemo)

在编译完成Message.idl,产生MessageCommon.dll和相应的MessageTypeSupport的.h头文件和.cpp文件(MessageTypeSupportImpl.h、MessageTypeSupportC.h)的基础上,新建PubSubDemo.sln和工程PubSubDemo.vcxproj,并开始编码,实现基于Message的发布和订阅流程。 1)新建基于Dia…

OPenDDS程序 的 实现+运行

标题DDS程序实现和运行 本文记录了Windows10环境下OpenDDS环境搭建&#xff0c;idl自定义&#xff0c;代码生成&#xff0c;代码编写的全过程。 一、环境搭建 1.详细情况请参考开发笔记&#xff1a;1. OpenDDS环境搭建-Windows 10.note 编译好后生成了两个文件夹“OpenDDS-3.…

OpenDDS-1

转自&#xff1a;软件开发.OpenDDS 设计智能座舱时ECU之间通信及与TSP通信选择使用OpenDDS是可以的&#xff0c;因此不少人都认为OpenDDS是属于汽车以太网&#xff08;Aumotive Ethernet&#xff0c;AE&#xff09;&#xff0c;但事实上autosar AE中定义中有SomeIP、DoIP、AVB…

Java程序调用OpenDDS

一、前言 前面我们用三篇博客介绍了 OpenDDS在WIndows上的环境配置 Windows下的OpenDDS编译&#xff08;超详细&#xff09;_山中野竹的博客-CSDN博客_opendds windows 三种方式运行发布订阅示例程序 OpenDDS运行示例&#xff08;Messenger&#xff09;程序_山中野竹的博客-C…