导读:本期聚焦于美谷创作的《DiskLruCache缓存框架的核心原理是什么?一文读懂Android磁盘缓存源码设计》,敬请观看详情。DiskLruCache是Android平台上广受推崇的磁盘缓存方案,它的源码被收录进AOSP官方文档,被众多图片加载框架借鉴。这篇文章从journal文件格式讲起,逐步拆解DiskLruCache的初始化流程、缓存的写入与读取逻辑、LRU淘汰算法的实现细节,以及日志文件的重建机制。你会了解到它如何用一行操作记录保证缓存一致性,为何编辑缓存需要通过Editor对象进行,以及valueCount参数在设计上的巧妙之处。文中附带关键源码片段与使用示例,帮助你把这套缓存思想迁移到自己的项目中。

DiskLruCache由Square公司的Jake Wharton开源,虽然没有进入Android SDK,却被Google官方文档推荐,Glide、Picasso等知名框架的磁盘缓存设计都能看到它的影子。它解决的核心问题是:当内存缓存因进程被杀而失效时,如何用一套可靠的磁盘缓存机制继续提供数据,同时防止缓存目录无限膨胀。这篇文章结合源码逐层拆解它的实现思路。

DiskLruCache缓存框架的核心原理是什么?一文读懂Android磁盘缓存源码设计

一、初始化流程与journal文件的作用

DiskLruCache的构造方法是私有的,外部必须通过静态方法open创建实例。open方法接收四个参数:缓存目录、应用版本号、valueCount和缓存上限大小。valueCount表示每个key对应多少个文件,图片场景下通常传1;如果需要同时缓存原图和缩略图,可以传2,这样一个key会对应两个缓存文件。

open内部首先会检查缓存目录下是否存在journal文件。这个文件是整个框架的记账本,记录了每一次缓存的增删改操作。如果journal文件不存在,说明是全新缓存,直接创建目录和文件;如果存在,则调用readJournal方法把日志逐行读入内存,重建一个LinkedHashMap结构的lruEntries集合,再通过processJournal方法过滤掉中间状态的操作记录。

journal文件的每一行格式是操作类型加key,常见的操作有五种:DIRTY表示某个key开始被编辑,CLEAN表示编辑完成且写入了指定字节数的文件,READ表示一次读取,REMOVE表示条目被删除,REWRITE则是重建日志时的清理标记。举例来说,写入一个缓存的全过程会先追加一行DIRTY记录,写完后追加CLEAN记录并带上文件长度。读取缓存时只追加READ行,不改动数据文件。

File directory = new File(getCacheDir(), "disk_cache");
DiskLruCache cache = DiskLruCache.open(directory, 1, 1, 64 * 1024 * 1024);
// 参数依次为:目录、App版本号、单个key对应的文件数、缓存总大小上限(字节)
DiskLruCache.Editor editor = cache.edit("image_key_001");
if (editor != null) {
    OutputStream os = editor.newOutputStream(0);
    // 把网络下载的图片字节写入输出流
    editor.commit(); // 成功则记录CLEAN
    // editor.abort(); // 失败则回滚,记录REMOVE
}

这里有个容易被忽略的细节:DIRTY和CLEAN必须成对出现。如果进程在写入过程中崩溃,journal里就只剩一条孤立的DIRTY记录,下次初始化时processJournal会发现这个key没有对应的CLEAN记录,直接把它当作垃圾条目从lruEntries中剔除,对应的临时文件也会被清理。这就是DiskLruCache保证一致性的关键手段——用追加式日志模拟事务语义。

二、缓存的写入、读取与LRU淘汰机制

写入缓存必须借助Editor对象。edit方法会检查该key是否已有正在进行的编辑,如果有则返回null,避免并发写冲突。Editor提供的newOutputStream返回的并不是普通的文件流,而是FaultHidingOutputStream,它会隐藏底层IO异常,把失败状态暂存起来,等commit时再统一判断。编辑过程产生的文件写在后缀为.tmp的临时文件里,只有commit成功后才会rename成正式文件,这也是典型的原子写方案。

读取走get方法。get会先把命中条目移到LinkedHashMap的尾部(accessOrder为true的访问序链表),再往journal追加一行READ记录,最后返回Snapshot对象。Snapshot持有该条目所有文件的输入流和一个长度数组,外部通过它读取数据。值得注意的是,get并不会校验文件是否真的存在,所以条目被删除后流会抛出FileNotFoundException,业务层需要自行容错。

淘汰逻辑集中在trimToSize方法里。每当commit一个条目,框架会累加size字段,一旦超过maxSize,就从lruEntries的头部开始逐个删除最久未使用的条目:先往journal写REMOVE记录,再删除磁盘上的实际文件,并递减size。除此之外还有一个trimToFileCount概念用于限制文件总数,不过需要通过反射方式修改journalFile限制,一般场景用不到。整个淘汰过程在同步锁内执行,保证多线程下的计数准确。

DiskLruCache.Snapshot snapshot = cache.get("image_key_001");
if (snapshot != null) {
    InputStream is = snapshot.getInputStream(0);
    // 把流解码为Bitmap并展示
    snapshot.close(); // 用完务必关闭
}
// 手动删除某个缓存
cache.remove("image_key_001");
// 立即把journal变更刷盘
cache.flush();

这里顺带提一下flush与close的区别。flush只是把内存中的日志缓冲写入journal文件,缓存对象仍可使用;而close会先flush再释放资源,关闭后的缓存对象不能再用。另外journal的写入并非每次都直接落盘,框架内部维护了一个redundantOpCount计数器,操作数达到一定阈值后触发rebuildJournal,这是下一节的内容。

三、journal重建与值得借鉴的设计细节

journal文件会随着READ操作的不断追加而膨胀,如果不加处理,几万次读取之后这个文件会大得离谱。DiskLruCache的处理方式是:每次追加操作让redundantOpCount自增,当它超过2000且达到lruEntries大小的两倍时,在后台线程调用rebuildJournal。重建的逻辑很直接——遍历内存中lruEntries的所有干净条目,为每个条目写一行CLEAN记录,生成新的journal.tmp文件,原子替换旧journal。重建完成后redundantOpCount归零,文件体积恢复到与条目数成正比的水平。

这种把全量状态定期快照、增量日志即时追加的设计,本质上是WAL(Write-Ahead Logging)思路的简化版,和SQLite、Redis的持久化策略异曲同工。理解这一点后你会发现,即使不直接使用DiskLruCache,这套journal机制也可以搬到自己的本地存储方案里,比如给接口数据做离线缓存。

还有几个细节值得留意。第一,key必须是合法文件名兼容的字符串,官方推荐用url的MD5值,避免特殊字符导致文件创建失败。第二,appVersion参数参与journal的校验,版本号变化会让旧缓存整体失效,适合在数据格式升级时强制刷缓存。第三,DiskLruCache在关键路径上都用了synchronized同步块而非读写锁,读多写少的场景下性能有一定损失,这也是后来一些框架改用自己实现的原因之一。不过对于绝大多数应用,这套实现的稳定性和正确性远比那点锁开销更重要。

总结来看,DiskLruCache的源码不到一千行,却完整覆盖了原子写入、日志恢复、LRU淘汰、容量控制四个问题。读懂它之后再看Glide的DiskLruCacheWrapper或OkHttp的缓存模块,会发现思路几乎一脉相承,学习性价比非常高。

DiskLruCacheAndroid缓存磁盘缓存源码修改时间:2026-09-03 23:37:17

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260903/49877.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。