一句话说透Android里面的Lrucache的使用及其原理

224 阅读2分钟

一句话总结:

用快递驿站储物柜类比理解:
LruCache就像智能储物柜系统,它的运行规则是:

1. 每个快递(数据)都有固定大小的格子  
2. 新快递来优先放空柜子  
3. 柜子满了就淘汰最久没取的快递(LRU规则)  
4. 经常取的快递位置会刷新到最新区域  

一、核心原理揭秘

1. 数据结构核心 - 双向链表+哈希表

// 源码核心实现(简化版)  
class LruCache<K, V>(maxSize: Int) {  
    private val cache = LinkedHashMap<K, V>(  
        0, 0.75f, true // true表示按访问顺序排序  
    )  
 
    // 当前缓存大小(字节/单位)  
    private var currentSize = 0  
    private val maxSize = maxSize  
}  

LinkedHashMap的魔法

  • 每次get/put操作会把元素移到链表尾部
  • 链表头部的元素就是最久未使用

2. 淘汰算法动态演示

假设最大容量=3,操作顺序:AB→C→A→D  
操作步骤          当前缓存状态       淘汰情况  
put(A)          [A]put(B)          [A→B]put(C)          [A→B→C]get(A)          [B→C→A]put(D)          [C→A→D]          淘汰B  

二、使用指南(三步搞定)

1. 基础初始化

// 创建图片缓存(最大10MB)  
val imageCache = object : LruCache<String, Bitmap>(10 * 1024 * 1024) {  
    // 计算每个Bitmap的内存占用  
    override fun sizeOf(key: String, value: Bitmap): Int {  
        return value.allocationByteCount   
    }  
 
    // 元素被移除时的回调(可做资源回收)  
    override fun entryRemoved(evicted: Boolean, key: String,   
        oldValue: Bitmap, newValue: Bitmap?) {  
        oldValue.recycle()   
    }  
}  

2. 常用操作API

// 添加缓存  
imageCache.put("cat.jpg",  bitmap)  
 
// 获取缓存(会更新元素位置)  
val bitmap = imageCache.get("cat.jpg")   
 
// 强制清理(保留最近50%的内容)  
imageCache.trimToSize(imageCache.maxSize()  / 2)  
 
// 获取缓存快照(线程安全拷贝)  
val snapshot = imageCache.snapshot()   

3. 配合协程使用

// 异步加载并缓存  
fun CoroutineScope.loadImageAsync(url:  String) = async(Dispatchers.IO) {  
    var bitmap = imageCache.get(url)   
    if (bitmap == null) {  
        bitmap = downloadImage(url)  
        imageCache.put(url,  bitmap)  
    }  
    return@async bitmap  
}  

四、避坑指南

坑1:哈希碰撞攻击

危险代码

// 使用默认hashCode可能被恶意攻击  
val cache = LruCache<HttpRequest, Response>(100)  

安全方案

// 启用安全哈希模式  
val safeCache = LruCache<String, Data>(  
    maxSize = 100,  
    hasher = { key ->   
        Security.getSecureHash(key)   
    }  
)  

坑2:内存计算误差

错误实现

override fun sizeOf(key: String, value: Bitmap): Int {  
    return 1 // 错误!导致实际内存超出限制  
}  

正确方案

// 使用官方API获取真实内存占用  
override fun sizeOf(key: String, value: Bitmap): Int {  
    return if (Build.VERSION.SDK_INT >= 33) {  
        value.allocationByteCount   
    } else {  
        value.byteCount   
    }  
}  

五、性能优化技巧

1. 分层缓存架构

// 三级缓存方案  
fun loadImage(url: String): Bitmap? {  
    // 1. 查内存缓存  
    var bitmap = memoryCache.get(url)   
    if (bitmap != null) return bitmap  
 
    // 2. 查磁盘缓存  
    bitmap = diskLruCache.get(url)   
    if (bitmap != null) {  
        memoryCache.put(url,  bitmap)  
        return bitmap  
    }  
 
    // 3. 网络下载  
    bitmap = download(url)  
    diskLruCache.put(url,  bitmap)  
    memoryCache.put(url,  bitmap)  
    return bitmap  
}  

2. 智能预加载

// 预测用户行为预加载  
fun preloadImages(visibleItems: List<String>) {  
    val prediction = Predictor.analyzeScrollTrend()   
    visibleItems.forEachIndexed  { index, url ->  
        if (index < prediction.loadAheadCount)  {  
            launch {  
                val bitmap = imageCache.get(url)  ?: download(url)  
                imageCache.put(url,  bitmap)  
            }  
        }  
    }  
}  

LruCache终极口诀:
链表哈希双结构,最近最少先淘汰
容量计算要精准,entryRemoved做清理
线程安全需注意,协程封装更高效
内存磁盘分层存,三级架构性能强
预加载,智能清,2025用法要记牢!