LRU缓存淘汰机制全揭秘:SDURLCache如何守护你的磁盘容量上限
【免费下载链接】SDURLCacheURLCache subclass with on-disk cache support on iPhone/iPad. Forked for speed!项目地址: https://gitcode.com/gh_mirrors/sdu/SDURLCache
SDURLCache 是一个为 iPhone/iPad 上 NSURLCache 提供磁盘缓存支持的开源库,它内置基于LRU 缓存淘汰算法的自动清理机制,确保磁盘缓存使用量永远不超过你设定的容量上限。本文将用通俗的方式拆解它的完整淘汰流程。
为什么磁盘缓存需要 LRU 淘汰机制?
移动端 App 的 HTTP 响应缓存如果只进不出,迟早会塞满磁盘:
- 缓存文件越写越多,磁盘容量上限被突破
- 淘汰策略选择不当,还会误删"最近还在用"的热数据
- 清理如果跑在主线程,会直接卡死 UI
LRU(Least Recently Used,最近最少使用)是工业界最经典的解法:容量超限时,优先淘汰最久没被访问过的条目,把空间留给高频使用的数据。SDURLCache 正是靠这套机制,把磁盘占用牢牢锁在diskCapacity之内。
第一步:给每条缓存建立"档案"
淘汰的前提是知道"谁多大、多久没用了"。SDURLCache 在缓存目录下维护一个 cacheInfo.plist 档案文件,里面只存两组关键数据:
| 档案键 | 内容 | 作用 |
|---|---|---|
accesses | 每条缓存的最后访问时间 | 供 LRU 排序 |
sizes | 每条缓存的磁盘大小 | 计算总占用 |
初始化时会把所有sizes求和,得到实时磁盘用量_diskCacheUsage(见 diskCacheInfo 加载逻辑)。每个缓存文件本身则是用URL 的 MD5 哈希命名落盘的,规则见 cacheKeyForURL。
💡 小技巧:定义
SDURLCACHE_DEBUG=1后,档案里还会额外记录每条缓存对应的原始 URL,方便调试(SDURLCache.h)。
核心揭秘:balanceDiskUsage 的三段式淘汰
当磁盘用量达到上限,balanceDiskUsage 方法执行完整的 LRU 淘汰,分三步走:
1️⃣ 先算"要腾多少空间"
capacityToSave = 当前磁盘用量 − diskCapacity只要用量没超上限,方法会直接 return,零开销——这是它高效的第一层原因。
2️⃣ 按访问时间排序,最旧的排最前
方法把accesses档案按时间戳升序排序,得到一份"从最冷到最热"的淘汰候选队列。
3️⃣ 逐个删除直到腾够空间
从队列头部(最久未访问的条目)开始删,每删一个就从capacityToSave里扣掉它的大小,直到欠款清零为止。删除动作由 removeCachedResponseForCachedKeys 完成:删磁盘文件、清档案记录、扣减用量计数,三步一气呵成。
注意这里的批量删除设计:先收集所有待删 Key,再一次性执行,避免边遍历边删造成状态错乱。
谁来触发淘汰?5 秒一醒的维护定时器
SDURLCache 没有"每次写入都立刻清理",而是用一个 5 秒周期的 GCD 定时器做周期性维护(maintenanceTimer):
- ⏰ 每 5 秒触发一次 periodicMaintenance
- 用量超过上限 → 在独立磁盘 IO 队列上执行 LRU 淘汰
- 没超上限但有脏数据 → 顺路把档案持久化到磁盘
- 执行完先挂起自己,等有新的缓存写入时再被唤醒(见 storeRequestToDisk 中的恢复逻辑)
这套"休眠—唤醒"设计让空闲期的 App 几乎不产生任何磁盘 IO,非常省电。
细节亮点:LRU 访问时间的"延迟写入"
缓存命中时是更新访问时间的最佳时机,但每次都写 plist 太费 IO。SDURLCache 的做法很聪明(cachedResponseForRequest):
- 命中磁盘缓存后,只在内存里更新访问时间,并打上"脏"标记
- 把磁盘 IO 和档案保存留给维护线程批量完成
- 命中的响应同时回填到内存缓存,下次访问更快
读写操作全部收敛到独立的磁盘队列串行执行,绝不阻塞主 Run Loop,这也是它敢叫"为速度而 Fork"的原因(README.md)。
写盘前的三道防护:垃圾数据根本不进库
淘汰机制再强,也不如"少存垃圾"有效。SDURLCache 在数据落盘前设置了层层闸门:
- 最小缓存间隔:默认 5 分钟内就要过期的响应直接不写盘,避免占着空间白过期(minCacheInterval 属性说明)
- 尊重服务器指令:
Cache-Control: no-store、Pragma: no-cache的响应一律拒收,过期时间解析逻辑见 expirationDateFromHeaders - 单条体积上限:单条响应比整个
diskCapacity还大?直接跳过,防止"一条缓存吃掉全部配额"(storeCachedResponse)
快速上手:三行代码开启磁盘缓存
SDURLCache *urlCache = [[SDURLCache alloc] initWithMemoryCapacity:1024*1024 diskCapacity:1024*1024*5 diskPath:[SDURLCache defaultCachePath]]; [NSURLCache setSharedURLCache:urlCache];替换默认共享缓存后,App 立即拥有磁盘 HTTP 缓存能力,容量与淘汰策略完全由你掌控。更多 API(如isCached:、removeAllCachedResponses)可在 SDURLCache.h 中查阅。
总结:一张图看懂容量守护全链路
新响应到达 → 三道防护过滤 → 异步写盘 → 用量+档案更新 ↓ 5秒维护定时器唤醒 → 用量 > diskCapacity? ↓ 是 LRU 排序 → 从最冷条目删起 → 腾够空间为止SDURLCache 的设计哲学可以概括为一句话:用最少的 IO,换取最稳定的磁盘占用上限。理解它的 LRU 淘汰链路,对你在任何项目中设计缓存容量管理都会有直接启发。
【免费下载链接】SDURLCacheURLCache subclass with on-disk cache support on iPhone/iPad. Forked for speed!项目地址: https://gitcode.com/gh_mirrors/sdu/SDURLCache
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考