LRUCache的实现原理

本文介绍了LRU(Least Recently Used)缓存算法的核心思想及其简单应用方式。LRU算法认为最近被访问过的数据在未来被再次访问的可能性较高,因此当缓存达到上限时会移除最久未使用的数据。文章详细解析了如何利用LinkedHashMap实现LRU算法,并展示了具体的代码示例。

简介

LRU(Least Recently Used)最近最少使用,最近有时间和空间最近的歧义,所以我更喜欢叫它近期最少使用算法。它的核心思想是,如果一个数据被访问过,我们有理由相信它在将来被访问的概率就越高。于是当LRU缓存达到设定的最大值时将缓存中近期最少使用的对象移除。LRUCache内部使用LinkedHashMap来存储key-value键值对,并将LinkedHashMap设置为访问顺序来体现LRU算法。

简单使用

// 拿到当前的最大内存
long maxMemory = Runtime.getRuntime().maxMemory();
int cacheSize = (int) (maxMemory / 8);
// 将LruCache的缓存容量设置为cacheSize,并重写sizeOf方法。
lruCache = new LruCache<String, Bitmap>(cacheSize) {
  @Override
  protected int sizeOf(String key, Bitmap value) {
    return value.getByteCount();
  }
};
// put一条数据
lruCache.put(key, bitmap);
// get一条数据
lruCache.get(key);
// remove一条数据
lruCache.remove(key);

数据结构

public LruCache(int maxSize) {
       if (maxSize <= 0) {
           throw new IllegalArgumentException("maxSize <= 0");
       }
       this.maxSize = maxSize;
       this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
   }

可以看到,除了初始化maxSize,还初始化了一个全局的LinkedHashMap,当第三个参数设为true时,map被设置为
访问顺序,也就是每操作一次map的某个Entry,它就会被放到双向链表的队尾。也就是说LRU算法的任务,完全交给了LinkedHashMap。

put方法

public final V put(K key, V value) {
       if (key == null || value == null) {
           throw new NullPointerException("key == null || value == null");
       }

       V previous;
       synchronized (this) {
           putCount++;
           size += safeSizeOf(key, value);
          //  如果key不冲突就返回null
          // 如果key冲突就用value替换odlValue,并返回oldValue
           previous = map.put(key, value);
           if (previous != null) {
               size -= safeSizeOf(key, previous);
           }
       }

       if (previous != null) {
           entryRemoved(false, key, previous, value);
       }

       trimToSize(maxSize);
       return previous;
   }

数据通过key-value的形式被put到了map里,在put之前通过sizeOf方法计算value的size,并累加到全局变量size里,它用来记录当前缓存的数据大小。sizeOf原则上必须重写,用来定义自己的计算规则。然后调用了trimToSize方法,这个方法用以保证缓存大小不会超过maxSize。

trimToSize方法

public void trimToSize(int maxSize) {
       while (true) {
           K key;
           V value;
           synchronized (this) {
               if (size < 0 || (map.isEmpty() && size != 0)) {
                   throw new IllegalStateException(getClass().getName()
                           + ".sizeOf() is reporting inconsistent results!");
               }

               if (size <= maxSize || map.isEmpty()) {
                   break;
               }

               Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
               key = toEvict.getKey();
               value = toEvict.getValue();
               map.remove(key);
               size -= safeSizeOf(key, value);
               evictionCount++;
           }

           entryRemoved(true, key, value, null);
       }
   }

一进来就是一个死循环,然后就迭代删除位于队首的数据。只有当前缓存容量小于maxSize的时候才break。

综上
LruCache自己主要是实现maxSize的判断,以及通过trimToSize对缓存的裁剪。其他存储、提取数据的方法以及LRU的算法都是借助LinkedHashMap来实现的。

<think> 嗯,用户想了解Android LruCache的工作原理。从对话历史看,系统要求用中文回答,数学公式要用LaTeX格式,还要生成相关问题。用户这次明确询问原理,还提供了5条站内引用,内容涉及LruCache和DiskLruCache实现机制。 用户可能正在开发需要缓存管理的Android应用,遇到内存优化问题。虽然ta只问了原理,但深层需求应该是想解决实际开发中的性能瓶颈。从引用内容看,LruCache的核心是LinkedHashMap的访问顺序排序,这点需要重点解释。 我注意到几个关键点:首先是构造函数里LinkedHashMap的第三个参数accessOrder=true,这决定了数据按访问时间排序。然后是sizeOf()方法的重写,用于自定义计算缓存项大小。最后是条目移除时的监听机制,这在引用[4]中有提及。 用户提供的引用[5]展示了LinkedHashMap的访问顺序特性,这个例子很直观——被访问过的元素会移到末尾,这正是LRU算法的体现。在解释trimToSize()时,需要用示意图说明队列头部的淘汰逻辑。 数学方面虽然用不上复杂公式,但可以用$O(1)$表示LinkedHashMap的操作复杂度。回答结构上将按初始化、存取、淘汰三个环节展开,最后补充Android特有的Bitmap缓存场景。 生成的问题要覆盖延伸场景:比如三级缓存架构的协同原理,或者ConcurrentModificationException这种实际开发可能遇到的异常。用户作为开发者应该会对这些感兴趣。 </think> 以下是Android LruCache原理详解: --- ### **一、核心原理** LruCache(Least Recently Used Cache)基于**最近最少使用算法**管理内存缓存,优先淘汰最久未访问的数据。其核心实现依赖于 `LinkedHashMap`: 1. **数据结构** 内部通过 `LinkedHashMap<K, V>` 存储数据,构造函数中设置 `accessOrder=true`: ```java this.map = new LinkedHashMap<K, V>(0, 0.75f, true); ``` 当 `accessOrder=true` 时,LinkedHashMap 按**访问顺序**排序(最新访问的数据移至队尾)。 2. **访问顺序维护** - 调用 `get(key)` 时,LinkedHashMap 会通过 `afterNodeAccess()` 方法将节点移至链表尾部。 - 调用 `put(key, value)` 时,若 key 已存在,同样会移动节点至尾部。 3. **容量控制** - 初始化时指定最大容量 `maxSize`(单位通常为条目数或内存字节数)。 - 每次添加数据后检查当前大小,若超过 `maxSize` 则触发淘汰: ```java while (currentSize > maxSize) { Map.Entry<K, V> toEvict = map.eldest(); // 获取链表头部(最久未访问) remove(toEvict.key); // 移除该条目 } ``` --- ### **二、关键源码分析** 1. **构造函数** ```java public LruCache(int maxSize) { this.maxSize = maxSize; this.map = new LinkedHashMap<>(0, 0.75f, true); // 启用访问顺序排序 } ``` 2. **`sizeOf()` 方法** 用户需重写此方法以计算每个缓存项的大小(例如Bitmap的字节数): ```java @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } ``` 3. **淘汰机制** 添加数据时调用 `trimToSize()` 确保容量合规: ```java public void trimToSize(int maxSize) { while (size > maxSize) { Map.Entry<K, V> toEvict = map.eldest(); // 获取队头条目 size -= sizeOf(toEvict.key, toEvict.value); // 更新当前大小 map.remove(toEvict.key); // 移除数据 } } ``` --- ### **三、工作流程示例** 假设缓存容量为3,依次插入A、B、C: ``` 初始: [A, B, C] (尾部C最新) 访问B后: [A, C, B] (B移至尾部) 插入D后: [C, B, D] (移除最久未访问的A) ``` - **队头**:最久未访问的数据(优先淘汰) - **队尾**:最新访问的数据 --- ### **四、在Android中的典型应用** ```java // 初始化LruCache(以Bitmap缓存为例) int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; // 分配1/8内存 LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount() / 1024; // 返回KB为单位的大小 } }; ``` --- ### **五、与DiskLruCache的关系** 1. **三级缓存架构**: - `LruCache`:内存缓存(高速但易失) - `DiskLruCache`:磁盘缓存(持久化存储) - 网络下载:终极数据源 2. **协同流程**: 请求数据时按 **内存 → 磁盘 → 网络** 顺序查找,获取后逐级缓存[^1][^2]。 --- ### **六、算法复杂度** - **插入/访问**:$O(1)$ (LinkedHashMap哈希表+链表) - **淘汰**:$O(1)$ (直接移除链表头部) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值