DiskLruCache 源码深度解析
1、DiskLruCache 概述
DiskLruCache 是一种基于 LRU(Least Recently Used)算法的磁盘缓存实现,由 Jake Wharton 开发,广泛应用于 Android 应用中。它提供了高效的文件系统缓存机制,特别适合缓存网络请求结果、图片等大型数据。
核心特性:
- 基于 LRU 算法,自动管理缓存大小
- 支持多线程并发操作
- 提供事务支持,保证缓存一致性
- 支持缓存键值对,键为字符串,值为文件
- 可配置最大缓存大小和条目数量
应用场景:
- 图片缓存(如 Glide、Picasso 的底层缓存)
- 网络请求结果缓存
- 大型数据的本地缓存
- 减少重复网络请求,提升应用性能
2、核心数据结构
2.1、缓存文件结构
DiskLruCache 在磁盘上的文件结构如下:
/缓存目录
/journal:日志文件,记录缓存操作
/0/:存储缓存条目的目录
/key.0:缓存数据文件
/1/
/key.0:缓存数据文件
...2.2、Journal 日志文件
Journal 文件记录了所有缓存操作,格式为:
- 第一行:版本号
- 第二行:应用版本号
- 第三行:值的数量(通常为 1)
- 第四行:空行
- 后续行:操作记录(DIRTY、CLEAN、REMOVE、READ)
2.3、内部数据结构
- LinkedHashMap<String, Entry> lruEntries:存储缓存条目,按访问顺序排序
- long size:当前缓存大小
- long maxSize:最大缓存大小
- int editBufferSize:编辑缓冲区大小
- int version:DiskLruCache 版本号
- int appVersion:应用版本号
- int valueCount:每个缓存键对应的值数量
3、核心方法解析
3.1、初始化与创建
// 创建 DiskLruCache
DiskLruCache cache = DiskLruCache.open(
directory, // 缓存目录
appVersion, // 应用版本号
valueCount, // 每个键对应的值数量
maxSize // 最大缓存大小
);核心实现:
1. 检查目录是否存在,不存在则创建
2. 读取并解析 journal 文件
3. 恢复缓存条目,构建 lruEntries 映射
4. 清理无效的缓存条目
3.2、获取缓存
// 获取缓存
DiskLruCache.Snapshot snapshot = cache.get(key);
if (snapshot != null) {
// 获取输入流
InputStream inputStream = snapshot.getInputStream(0);
// 使用缓存数据
// ...
snapshot.close();
}核心实现:
1. 检查 key 是否合法
2. 从 lruEntries 中查找 Entry
3. 检查 Entry 是否有效
4. 记录 READ 操作到 journal
5. 返回 Snapshot 对象
3.3、写入缓存
// 开始编辑
DiskLruCache.Editor editor = cache.edit(key);
if (editor != null) {
try {
// 获取输出流
OutputStream outputStream = editor.newOutputStream(0);
// 写入数据
// ...
// 提交编辑
editor.commit();
} catch (IOException e) {
// 放弃编辑
editor.abort();
}
}核心实现:
1. 检查 key 是否合法
2. 检查是否已存在编辑
3. 创建临时文件
4. 记录 DIRTY 操作到 journal
5. 提交时记录 CLEAN 操作
6. 更新 LRU 顺序和缓存大小
3.4、删除缓存
// 删除缓存
cache.remove(key);核心实现:
1. 检查 key 是否合法
2. 从 lruEntries 中移除 Entry
3. 删除对应的缓存文件
4. 记录 REMOVE 操作到 journal
5. 更新缓存大小
3.5、清理缓存
// 清理超出大小的缓存
cache.trimToSize(maxSize);核心实现:
1. 计算超出的大小
2. 按 LRU 顺序移除最旧的条目
3. 直到缓存大小符合要求
4、并发控制与事务
4.1、并发控制
DiskLruCache 使用以下机制保证并发安全:
- synchronized 锁:保护关键操作
- 原子操作:确保数据一致性
- 文件锁:防止多进程同时访问
- 编辑锁:确保同一键的编辑操作互斥
4.2、事务支持
DiskLruCache 通过以下步骤保证事务完整性:
1. 写入时创建临时文件
2. 提交时原子性地重命名文件
3. journal 日志记录所有操作
4. 恢复时通过 journal 重建状态
4.3、异常处理
- 写入失败时自动回滚
- 恢复时处理损坏的 journal
- 清理无效的缓存文件
- 确保缓存状态一致
5、源码优化技巧
5.1、性能优化
- 使用缓冲区减少 I/O 操作
- 延迟写入 journal,批量提交
- 使用 LinkedHashMap 实现 LRU 算法
- 定期清理过期缓存
5.2、内存优化
- 避免内存泄漏,及时关闭流
- 合理设置缓存大小
- 使用 SoftReference 管理缓存对象
- 避免一次性加载过多数据
5.3、安全性
- 验证 key 的合法性
- 防止缓存注入攻击
- 处理恶意文件
- 权限控制
6、使用示例与最佳实践
6.1、基本使用示例
// 初始化
File cacheDir = getCacheDir(context);
DiskLruCache cache = DiskLruCache.open(
cacheDir,
BuildConfig.VERSION_CODE,
1,
10 * 1024 * 1024 // 10MB 缓存
);
// 写入缓存
String key = generateKey(url);
DiskLruCache.Editor editor = cache.edit(key);
if (editor != null) {
OutputStream os = editor.newOutputStream(0);
// 下载并写入数据
downloadToStream(url, os);
editor.commit();
}
// 读取缓存
DiskLruCache.Snapshot snapshot = cache.get(key);
if (snapshot != null) {
InputStream is = snapshot.getInputStream(0);
// 使用缓存数据
bitmap = BitmapFactory.decodeStream(is);
snapshot.close();
}
// 关闭缓存
cache.close();6.2、最佳实践
- 合理设置缓存大小(通常为 5-20MB)
- 使用固定长度的 key(如 MD5 哈希)
- 及时关闭 Snapshot 和 Editor
- 定期清理缓存
- 处理网络异常和缓存过期
6.3、注意事项
- 不要在主线程执行缓存操作
- 处理缓存文件损坏的情况
- 考虑应用版本升级时的缓存兼容性
- 监控缓存使用情况
6.4、总结
DiskLruCache 是一个设计精良的磁盘缓存实现,通过 LRU 算法和事务支持,提供了高效、可靠的缓存机制。它的源码结构清晰,实现了完整的缓存生命周期管理,是学习文件系统缓存的优秀范例。
关键要点:
- 基于 LRU 算法自动管理缓存
- 支持并发操作和事务
- 提供简单易用的 API
- 适用于各种缓存场景
掌握 DiskLruCache 的使用和原理,对于开发高性能、响应迅速的 Android 应用非常重要,是 Android 开发者必备的技能之一。
