适合iOS开发的一种缓存策略YYCache库 的原理

YYCache 是 iOS 上一个高性能的缓存框架,它由内存缓存 YYMemoryCache 和磁盘缓存 YYDiskCache 两部分组成。

核心总览

YYCache 的核心设计目标是 高效、线程安全和高性能。它通过以下方式实现这一目标:

  1. 分层设计:内存缓存提供极速访问,磁盘缓存提供大容量存储。
  2. LRU 淘汰算法:两者都使用 LRU 算法来管理缓存项,确保高频数据留在缓存中。
  3. 数据结构优化
    • 内存缓存 :结合 NSDictionary 和双向链表。
    • 磁盘缓存:结合 SQLite 和文件系统。
  4. 锁策略优化 :使用 pthread_mutex 锁来保证线程安全,性能优于 @synchronizeddispatch_semaphore

为了更直观地理解其核心工作原理,我们可以用以下流程图来展示其数据结构和关键操作:

上图揭示了YYCache的核心架构,下面我们来详细拆解图中各个部分的工作原理。

一、YYMemoryCache (内存缓存) 原理

YYMemoryCache 使用了一种非常经典且高效的数据结构组合:双向链表 + 哈希表

1. 核心数据结构:_YYLinkedMapNode_YYLinkedMap

  • _YYLinkedMapNode:链表节点。

    objc 复制代码
    @interface _YYLinkedMapNode : NSObject {
        @package
        __unsafe_unretained _YYLinkedMapNode *_prev; // 指向上一节点
        __unsafe_unretained _YYLinkedMapNode *_next; // 指向下一节点
        id _key;      // 缓存的键
        id _value;    // 缓存的值
        NSUInteger _cost;   // 开销成本(用于成本计算)
        NSTimeInterval _time; // 访问时间
    }
    @end
  • _YYLinkedMap:一个双向链表,用于管理所有节点。

    objc 复制代码
    @interface _YYLinkedMap : NSObject {
        @package
        CFMutableDictionaryRef _dic; // 哈希表,用于O(1)的存取
        NSUInteger _totalCost;      // 总开销
        NSUInteger _totalCount;     // 总数量
        _YYLinkedMapNode *_head;    // 链表头(MRU,最近使用)
        _YYLinkedMapNode *_tail;    // 链表尾(LRU,最久未使用)
    }
    @end

2. 工作原理

存取过程与LRU管理

其工作流程可以精确地描述为以下步骤:

sequenceDiagram participant A as Client(客户端) participant M as YYMemoryCache participant D as _dic (哈希表) participant L as 双向链表 A->>M: setObject:forKey: M->>D: 通过key查找节点 alt 节点已存在 M->>L: 更新节点value,将节点移至_head else 节点不存在 M->>M: 创建新节点 M->>D: 插入新节点 M->>L: 将新节点插入至_head M->>M: _totalCount++, _totalCost++ loop 超过限制(count/cost) M->>L: 移除_tail节点(LRU) M->>D: 删除对应key M->>M: 更新_totalCount, _totalCost end end A->>M: objectForKey: M->>D: 通过key查找节点 alt 节点存在 M->>L: 将节点移至_head M->>A: 返回value else 节点不存在 M->>A: 返回nil end

线程安全YYMemoryCache 使用 pthread_mutex 锁来保证上述所有操作(_dic 的读写、链表的修改)的线程安全性。它在每个操作开始时加锁,结束时解锁。


二、YYDiskCache (磁盘缓存) 原理

YYDiskCache 的设计更为复杂,它采用了一种智能的混合存储策略,根据 value 的大小选择不同的存储方式,以在性能和空间之间取得平衡。

1. 核心思想:SQLite + 文件系统

  • SQLite 数据库

    • 存储所有的 元数据(key, 文件名,大小,访问时间等)。
    • 如果 value 很小(例如小于 16KB),直接将其作为 BLOB 数据存储在数据库的某一列中
    • 优势:对于小数据,读写非常快,并且数据库事务保证了操作的原子性。
    • 方便实现 LRU 淘汰算法,只需要通过 SQL 语句操作元数据即可。
  • 文件系统

    • 如果 value 很大(例如大于 16KB),则将其写入单独的文件,在数据库中只记录其文件名和路径。
    • 优势:避免大文件塞满 SQLite 数据库,导致性能下降。文件系统对于大文件的读写效率更高。

2. 工作流程

存储过程:

  1. 根据 key 在数据库中查询记录。
  2. 判断 value 的数据大小。
  3. 小数据 :直接写入 SQLite 的 data 列。如果之前是文件存储,则删除对应的文件。
  4. 大数据 :将数据写入一个文件,并在数据库的 filename 列记录文件名。如果之前 SQLite 的 data 列有数据,则清空。
  5. 更新数据库中的元信息(大小、访问时间等)。

读取过程:

  1. 根据 key 从数据库中查询记录。
  2. 如果记录中有文件名(filename 不为空),则从文件系统中读取该文件。
  3. 如果记录中没有文件名,则直接从数据库的 data 列读取数据。
  4. 更新访问时间 :每次读取后,都会在数据库中更新该记录的 last_access_time 字段,这对于实现 LRU 至关重要。

淘汰机制:

  1. 当磁盘缓存的总大小或总数量超过限制时,触发清理。
  2. 通过一条 SQL 查询,按照 last_access_time 升序排列(最久未使用的在前),获取需要淘汰的项。
  3. 根据查询结果,如果该项有文件,则删除文件;最后,从数据库中删除该记录。

三、YYCache 的整体协作

  1. 写入缓存

    • 先写入 YYMemoryCache
    • 再异步写入 YYDiskCache
  2. 读取缓存

    • 首先在 YYMemoryCache 中查找,找到则返回并更新链表。
    • 如果内存中没有,则去 YYDiskCache 中查找。
    • 如果在磁盘中找到,则将其返回给用户,并根据需要 (可配置)写回 YYMemoryCache,以便下次快速访问。

总结

特性 YYMemoryCache YYDiskCache
存储介质 内存 磁盘 (SQLite + 文件系统)
数据结构 双向链表 + 哈希表 数据库表 + 文件
线程安全 pthread_mutex 串行队列 + dispatch_semaphore
淘汰算法 LRU (链表移动) LRU (SQL 按时间排序)
性能 极快,O(1) 较快,对小数据优化好
容量 受内存限制 受磁盘空间限制

YYCache 的成功在于其对经典算法和数据结构的深刻理解,并结合 iOS 平台特性进行了精妙的工程优化,使其成为了一个非常出色和可靠的缓存组件。

相关推荐
是宇写的啊1 小时前
算法—滑动窗口
算法
风筝在晴天搁浅2 小时前
代码随想录 509.斐波那契数
数据结构·算法
落落落sss2 小时前
java实现排序
java·数据结构·算法
hour_go2 小时前
《微服务系统故障诊断》:核心概念、技术流派与未来展望
微服务·云原生·架构
limenga1022 小时前
支持向量机(SVM)深度解析:理解最大间隔原理
算法·机器学习·支持向量机
coder江3 小时前
二分查找刷题总结
算法
ZePingPingZe3 小时前
分布式、Spring Boot微服务、垂直拆分、水平拆分、分库分表详解及关系梳理
分布式·架构
坚持就完事了4 小时前
蓝桥杯中Python常用的库与模块
python·算法
立志成为大牛的小牛4 小时前
数据结构——四十四、平衡二叉树的删除操作(王道408)
数据结构·学习·程序人生·考研·算法