深入浅出DiskLruCache原理
一、DiskLruCache是什么?------手机里的文件仓库管理员
想象DiskLruCache就像是你手机里的一个智能仓库管理员:
- 专门职责:管理图片等文件在手机存储中的缓存
- 工作原则:最近最少使用的文件优先淘汰(LRU算法)
- 存储位置:/data/data/<包名>/cache 或 SD卡缓存目录
它不同于内存缓存(速度极快但容量小),专门管理磁盘上的缓存文件,是图片三级缓存中的重要一环。
二、核心工作原理------仓库管理员的日常工作
1. 仓库结构设计
每个DiskLruCache仓库由以下部分组成:
erlang
cache_dir/
├── journal ← 仓库的账本文件(最重要!)
├── file.0 ← 实际缓存文件
├── file.1
└── ...
2. 关键工作流程
存文件(put):
java
// 就像仓库管理员收到新货
DiskLruCache.Editor editor = cache.edit("image1"); // 申请入库单
editor.newOutputStream(0).write(imageData); // 把货物放进临时位置
editor.commit(); // 正式入库记账
取文件(get):
java
// 就像有人来取货
DiskLruCache.Snapshot snapshot = cache.get("image1"); // 查账本
if (snapshot != null) {
InputStream inputStream = snapshot.getInputStream(0); // 打开货架
Bitmap bitmap = BitmapFactory.decodeStream(inputStream); // 取出货物
snapshot.close(); // 关好货架
}
清理仓库(trimToSize):
java
// 定期盘点,清理最久未使用的货物
cache.flush(); // 先确保账本最新
cache.trimToSize(); // 执行清理(默认在put/get时自动触发)
三、核心文件解析------账本(journal)的秘密
journal文件是DiskLruCache的核心,记录所有操作日志,格式如下:
lua
libcore.io.DiskLruCache ← 魔数(标识文件类型)
1 ← 版本号
100 ← APP版本号
2 ← 每个key对应value数
← 空行
CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
DIRTY 335c4c6028171cfddfbaae1a9c313c52
REMOVE 335c4c6028171cfddfbaae1a9c313c52
READ 335c4c6028171cfddfbaae1a9c313c52
行类型说明:
- DIRTY:表示正在写入(此时文件可能不完整)
- CLEAN:表示成功写入(后面跟着文件大小)
- REMOVE:表示已删除
- READ:表示访问记录(用于LRU判断)
四、LRU淘汰算法------仓库的清理规则
DiskLruCache的清理策略就像仓库管理员的淘汰规则:
- 每次访问文件都会在journal中记录READ
- 当缓存大小超过限制时:
- 从LinkedHashMap尾部开始检查(最久未使用)
- 删除REMOVE标记的文件
- 直到缓存大小低于限制
java
// 简化版LRU实现逻辑
while (size > maxSize) {
Entry toEvict = lruEntries.values().iterator().next();
for (File file : toEvict.cleanFiles) {
size -= file.length();
file.delete(); // 删除实际文件
}
lruEntries.remove(toEvict.key); // 从记录中移除
}
五、关键特性解析
1. 文件命名机制
- 原始key会经过MD5处理(避免特殊字符问题)
- 每个key对应多个文件(通常图片缓存用1个)
2. 线程安全设计
- 通过synchronized保证操作原子性
- 所有文件操作必须通过Journal记录
3. 崩溃恢复
- 启动时会读取journal文件重建内存状态
- DIRTY状态但没有CLEAN记录的文件会被删除
4. 版本兼容
- 头部的APP版本号变化时会清空缓存
java
// 初始化时检查版本
if (journalReader.readInt() != appVersion) {
deleteAll(); // 版本不同则清理
}
六、使用示例------完整缓存图片流程
1. 初始化缓存
java
File cacheDir = getDiskCacheDir(context, "image_cache");
int cacheSize = 50 * 1024 * 1024; // 50MB
DiskLruCache cache = DiskLruCache.open(cacheDir, 1, 1, cacheSize);
2. 存储图片
java
String imageUrl = "http://example.com/image.jpg";
String key = generateKey(imageUrl); // 通常用URL的MD5
DiskLruCache.Editor editor = cache.edit(key);
try (OutputStream out = editor.newOutputStream(0)) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
editor.commit();
} catch (IOException e) {
editor.abort();
}
3. 读取图片
java
DiskLruCache.Snapshot snapshot = cache.get(key);
if (snapshot != null) {
InputStream in = snapshot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(in);
snapshot.close();
return bitmap;
}
4. 清理缓存
java
// 手动触发清理
cache.flush(); // 确保journal更新
cache.trimToSize();
// 或者删除全部
cache.delete();
七、优化技巧
1. 合理设置缓存大小
- 建议值:10MB-100MB
- 计算公式:
java
// 可用空间的1/8,但不超过50MB
long available = getUsableSpace(cacheDir);
long cacheSize = Math.min(available / 8, 50 * 1024 * 1024);
2. 定期维护
java
// 每周清理一次
if (System.currentTimeMillis() > lastCacheCheck + WEEK_IN_MILLIS) {
cache.trimToSize();
lastCacheCheck = System.currentTimeMillis();
}
3. 异常处理
java
try {
// 缓存操作
} catch (IOException e) {
cache.delete(); // 严重错误时重建缓存
}
八、常见问题解答
1. 为什么我的缓存文件变少了?
- 可能触发了LRU淘汰
- 可能APP版本升级导致清空
- 可能用户手动清理了缓存
2. journal文件可以删除吗?
- 不可以!除非同时删除所有缓存文件
- journal损坏会导致缓存不可用
3. 如何查看缓存内容?
java
for (DiskLruCache.Entry entry : cache.snapshot().values()) {
Log.d("Cache", "Key: " + entry.getKey() + ", Size: " + entry.getLength());
}
九、总结
DiskLruCache就像个精明的仓库管理员:
- 严格记账:journal文件记录所有操作
- 智能清理:LRU算法自动淘汰旧文件
- 安全可靠:崩溃后能恢复一致状态
关键设计要点:
- 文件+索引分离:实际文件与journal配合
- LRU实现:通过LinkedHashMap+访问记录
- 原子操作:通过DIRTY/CLEAN状态机制
使用记住三点:
- 初始化要设置合理大小
- 操作后及时flush()
- 定期维护trimToSize()
掌握DiskLruCache原理,你就能为APP打造高效的磁盘缓存系统!