LRU 缓存
问题
LruCache 和 DiskLruCache 的原理是什么?
答案
LruCache(内存缓存)
LRU(Least Recently Used)淘汰最近最少使用的数据。Android 的 LruCache 基于 LinkedHashMap 实现:
// Bitmap 内存缓存
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
val cacheSize = maxMemory / 8 // 使用 1/8 最大内存
val memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
return bitmap.byteCount / 1024 // KB
}
override fun entryRemoved(
evicted: Boolean, key: String,
oldValue: Bitmap, newValue: Bitmap?
) {
// 被淘汰的 Bitmap 放入 BitmapPool 复用
bitmapPool.put(oldValue)
}
}
DiskLruCache(磁盘缓存)
Jake Wharton 的 DiskLruCache 使用 journal 文件记录操作日志:
journal 文件格式
libcore.io.DiskLruCache
1
100
2
DIRTY key1
CLEAN key1 1024 512
READ key1
DIRTY key2
CLEAN key2 2048 0
REMOVE key1
DIRTY:开始写入CLEAN:写入成功,后跟每个文件的大小READ:读取操作REMOVE:删除
完整三级缓存实现
class ImageCache(context: Context) {
private val memoryCache: LruCache<String, Bitmap>
private val diskCache: DiskLruCache
init {
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
memoryCache = LruCache(maxMemory / 8)
val cacheDir = File(context.cacheDir, "images")
diskCache = DiskLruCache.open(cacheDir, 1, 1, 50 * 1024 * 1024) // 50MB
}
fun get(key: String): Bitmap? {
// L1: 内存
memoryCache.get(key)?.let { return it }
// L2: 磁盘
val snapshot = diskCache.get(key.md5()) ?: return null
val bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0))
bitmap?.let { memoryCache.put(key, it) } // 回填内存缓存
return bitmap
}
fun put(key: String, bitmap: Bitmap) {
memoryCache.put(key, bitmap)
val editor = diskCache.edit(key.md5()) ?: return
editor.newOutputStream(0).use { stream ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
}
editor.commit()
}
}
常见面试问题
Q1: 为什么 LruCache 用 LinkedHashMap 而不是 HashMap?
答案:
LinkedHashMap 构造时传入 accessOrder = true,每次 get() 会将被访问的元素移到链表尾部。这样链表头部就是最近最少使用的元素,淘汰时直接移除头部即可,时间复杂度 。HashMap 没有这种访问顺序维护能力。
Q2: DiskLruCache 的 journal 文件有什么作用?
答案:
journal 是操作日志文件,记录了所有的 DIRTY/CLEAN/READ/REMOVE 操作。作用是在 App crash 或异常退出后恢复缓存状态——重启时重放 journal 重建内存索引,未完成的 DIRTY 操作(没有对应的 CLEAN)会被清理掉,保证数据完整性。