LruCache实现原理

news2025/4/9 13:29:45

序、慢慢来才是最快的方法。

回顾

LRU (Least Recently Used)最近最少策略是最常用的缓存淘汰策略。LRU 策略会记录各个数据块的访问 “时间戳” ,最近最久未使用的数据最先被淘汰。与其他几种策略相比,LRU 策略利用了 “局部性原理”,平均缓存命中率更高。

FIFO 与 LRU 策略

经过总结,我们可以定义一个缓存系统的基本操作:

  • 操作 1 - 添加数据: 先查询数据是否存在,不存在则添加数据,存在则更新数据,并尝试淘汰数据;
  • 操作 2 - 删除数据: 先查询数据是否存在,存在则删除数据;
  • 操作 3 - 查询数据: 如果数据不存在则返回 null;
  • 操作 4 - 淘汰数据: 添加数据时如果容量已满,则根据缓存淘汰策略一个数据。

在 Java 标准库中,已经提供了一个通用的哈希链表 —— LinkedHashMap。使用 LinkedHashMap 时,主要关注 2 个 API:

  • accessOrder 标记位: LinkedHashMap 同时实现了 FIFO 和 LRU 两种淘汰策略,默认为 FIFO 排序,可以使用 accessOrder 标记位修改排序模式。
  • removeEldestEntry() 接口: 每次添加数据时,LinkedHashMap 会回调 removeEldestEntry() 接口。开发者可以重写 removeEldestEntry() 接口决定是否移除最早的节点(在 FIFO 策略中是最早添加的节点,在 LRU 策略中是最久未访问的节点)。

1.LruCache源码分析

1.1LruCache 的 API

LruCache 是 Android 标准库提供的 LRU 内存缓存框架,基于 Java LinkedHashMap 实现,当缓存容量超过最大缓存容量限制时,会根据 LRU 策略淘汰最久未访问的缓存数据。

用一个表格整理 LruCache 的 API:

1.2 LruCache 的属性

LruCache 的属性比较简单,除了多个用于数据统计的属性外,核心属性只有 3 个:

  • 1、size: 当前缓存占用;
  • 2、maxSize: 最大缓存容量;
  • 3、map: 复用 LinkedHashMap 的 LRU 控制能力。

LruCache.java

public class LruCache<K, V> {
    // LRU 控制
    private final LinkedHashMap<K, V> map;

    // 当前缓存占用
    private int size;
    // 最大缓存容量
    private int maxSize;

    // 以下属性用于数据统计

    // 设置数据次数
    private int putCount;
    // 创建数据次数
    private int createCount;
    // 淘汰数据次数
    private int evictionCount;
    // 缓存命中次数
    private int hitCount;
    // 缓存未命中数
    private int missCount;
}

1.3 LruCache 的构造方法

LruCache 只有 1 个构造方法。

由于缓存空间不可能设置无限大,所以开发者需要在构造方法中设置缓存的最大内存容量 maxSize

LinkedHashMap 对象也会在 LruCache 的构造方法中创建,并且会设置 accessOrder 标记位为 true,表示使用 LRU 排序模式。

LruCache.java #构造方法

  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);
    }

使用示例

 private static final int CACNHE_SIZE = 4 * 1024 * 1024;
 LruCache<String, Bitmap> lruCache = new LruCache<String, Bitmap>(CACNHE_SIZE);

1.4测量数据单元的内存占用

开发者需要重写 LruCache#sizeOf() 测量缓存单元的内存占用量,否则缓存单元的大小默认视为 1,相当于 maxSize 表示的是最大缓存数量。

// LruCache 内部使用
private int safeSizeOf(K key, V value) {
    // 如果开发者重写的 sizeOf 返回负数,则抛出异常
    int result = sizeOf(key, value);
    if (result < 0) {
        throw new IllegalStateException("Negative size: " + key + "=" + value);
    }
    return result;
}

// 测量缓存单元的内存占用
protected int sizeOf(K key, V value) {
    // 默认为 1
    return 1;
}



 LruCache<String, Bitmap> lruCache = new LruCache<String, Bitmap>(CACNHE_SIZE){

            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };

1.5 添加数据与淘汰数据

LruCache 添加数据的过程基本是复用 LinkedHashMap 的添加过程,我将过程概括为 6 步:

  • 1、统计添加计数(putCount);
  • 2、size 增加新 Value 内存占用;
  • 3、设置数据(LinkedHashMap#put);
  • 4、size 减去旧 Value 内存占用;
  • 5、数据移除回调(LruCache#entryRemoved);
  • 6、自动淘汰数据:在每次添加数据后,如果当前缓存空间超过了最大缓存容量限制,则会自动触发 trimToSize() 淘汰一部分数据,直到满足限制。

淘汰数据的过程则是完全自定义,我将过程概括为 5 步:

  • 1、取最找的数据(LinkedHashMap#eldest);
  • 2、移除数据(LinkedHashMap#remove);
  • 3、size 减去旧 Value 内存占用;
  • 4、统计淘汰计数(evictionCount);
  • 5、数据移除回调(LruCache#entryRemoved);
  • 重复以上 5 步,满足要求或者缓存为空,才会退出。

疑问 1:为什么 LruCache 不支持 null 作为 Key 或 Value?

其实并没有一定不能为 null 的理由,我的理解是 Google 希望降低 LruCache 的理解成本。如果允许 Value 为 null,那么当 LruCache 需要计算 Value 的 size 时,Value 为 null 默认应该当作 0 还是当作 1呢?

再者,如果业务开发确实有 Key 或 Value 的需求,也可以选择重写 LruCache 的相关方法,或者直接自实现一个 LruCache,这都是可以接受的方案。例如,在 Android Glide 图片框架中的 LruCache 就是自实现的。

总结

  • 1、LruCache 是 Android 标准库提供的 LRU 内存缓存框架,基于 Java LinkedHashMap 实现,当缓存容量超过最大缓存容量限制时,会根据 LRU 策略淘汰最久未访问的缓存数据;

  • 2、LruCache 需要重写 sizeOf() 测量缓存单元的内存占用量,否则缓存单元的大小默认视为 1,相当于 maxSize 表示的是最大缓存数量;

  • 3、LruCache 放弃了 LinkedHashMap#removeEldestEntry() 接口,而是自己实现了 trimToSize() 淘汰方法;

参考

Android 开源库 #8 Android 内存缓存框架 LruCache 的实现原理,手写试试? - 掘金

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1089080.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Sui账户抽象消除用户使用障碍,让大规模用户使用区块链成为可能

Sui通过其本机语言和两个特定功能实现了账户抽象&#xff0c;使账户管理中更加细节化的过程自动化。无论是zkLogin还是赞助交易&#xff0c;都简化了用户的使用过程&#xff0c;而Sui Move的基本结构则使开发人员能够提供丝滑的体验。 最近&#xff0c;随着区块链寻求扩大其用…

Flink(林子雨慕课课程)

文章目录 12.Flink12.1 Flink简介12.2 为什么要选择Flink12.3 Flink应用场景12.4 Flink技术栈、体系架构和编程模型12.5 Flink的安装和编程实战 12.Flink 12.1 Flink简介 企业的处理架构已经由传统数据处理架构和大数据Lamda架构向流处理架构演变 Flink实现了Goole Dataflow…

配置nginx的虚拟主机

1.基于域名的虚拟主机 vim /usr/local/nginx/conf/nginx.conf 复制一个 cd /var/www/html/ mkdir kgc accp cd kgc/ vim index.html this is kgc! cd .. cd accp this is accp! vim /etc/hosts systemctl restart nginx 2.基于ip的虚拟主机 ifconfig ens33:0 192.168…

如何生成SSH服务器的ed25519公钥SHA256指纹

最近搭建ubuntu服务器&#xff0c;远程登录让确认指纹&#xff0c;研究一番搞懂了&#xff0c;记录一下。 1、putty 第一次登录服务器&#xff0c;出现提示&#xff1a; 让确认服务器指纹是否正确。 其中&#xff1a;箭头指向的 ed25519 :是一种非对称加密的签名方法&#xf…

AMEYA360:北京君正集成电路多核异构跨界处理器X2000

• 双XBurst2核&#xff0c;主频1.2GHz • 跨界第三核XBurst0(240MHz)&#xff0c;面向安全管理和实时控制 • H.264编、解码器1080P30fps • 内置LPDDR3 128MB • 双摄Mipi接口双ISP&#xff0c;可实时同步 • 丰富的外设接口 应用领域 • 智能音频&#xff1a;智能音箱&#…

ubuntu安装datasophon问题记录

问题描述: 主机agent分发报红 解决步骤一: 修改datasophon-worker.tar.gz文件 解压/opt/datasophon/DDP/packages目录下的datasophon-worker.tar.gz文件修改datasophon-worker/bin目录下的datasophon-worker.sh文件 . /etc/profile解决步骤二: chkconfig命令不存在 当执行ch…

人事管理系统springboot42

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

龙迅LT7911UXC 是一款高性能TYPE-C/DP/EDP转换四端口MIPI/LVDS的芯片,还支持图像处理

龙迅LT7911UXC 1.描述&#xff1a; LT7911UXC是一款用于VR/显示应用的高性能Type-C/DP1.4a到MIPI或LVDS芯片。HDCP RX作为 HDCP中继器的上游端&#xff0c;可以与其他芯片的HDCP TX协同工作&#xff0c;实现中继器的功能。对于DP1.4a 输入&#xff0c;LT7911UXC可以配置为1…

基于LoRa的远程气象站:实现远程气象监测与数据传输

随着物联网技术的不断发展&#xff0c;基于无线通信的远程气象监测系统得以广泛应用。本文将介绍一种基于LoRa技术的远程气象站&#xff0c;通过LoRa模块实现气象数据的远程采集和传输&#xff0c;为气象监测提供了一种高效、低功耗的解决方案。 LoRa技术概述 LoRa&#xff08…

论文阅读》用语义解耦改进共情对话生成 2022 IJCKG

《论文阅读》用语义解耦改进共情对话生成 前言简介相关知识对抗学习模型架构Semantics DecouplerEmpathetic Generator损失函数前言 论文阅读不迷路! 今天为大家带来的是《Improving Empathetic Dialogue Generation with Semantics Decoupling》 出版:IJCKG(International…

Variations-of-SFANet-for-Crowd-Counting记录

论文&#xff1a;Encoder-Decoder Based Convolutional Neural Networks with Multi-Scale-Aware Modules for Crowd Counting 论文链接&#xff1a;https://arxiv.org/abs/2003.05586 源码链接&#xff1a;GitHub - Pongpisit-Thanasutives/Variations-of-SFANet-for-Crowd-C…

如何用BI制作图表组合?

BI&#xff08;Business Intelligence&#xff09;是一种通过收集、分析和可视化数据来帮助企业做出决策的技术和工具。在BI中&#xff0c;制作图表组合是一种常见的方式&#xff0c;可以将不同的图表类型组合在一起&#xff0c;以更全面地呈现数据。 下面将详细介绍如何使用B…

行业追踪,2023-10-13

自动复盘 2023-10-13 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

Redis分布式锁最牛逼的实现(Java 版,最牛逼的实现方式)

写在前面的话 分布式锁一般有三种实现方式&#xff1a;1. 数据库乐观锁&#xff1b;2. 基于Redis的分布式锁&#xff1b;3. 基于ZooKeeper的分布式锁。 本篇博客将介绍第二种方式&#xff0c;基于Redis实现分布式锁。 为什么需要分布式锁&#xff1f; 在单机环境下编写多线…

WAF绕过-信息收集之反爬虫延时代理池 46

老师用的阿里云的服务器&#xff0c;装了宝塔和安全狗&#xff0c; 演示案例 Safedog-默认拦截机制分析绕过-未开CC 没有打开防止流量攻击的安全狗&#xff0c; 而这里&#xff0c;get请求可以直接看到返回结果&#xff0c;而head就不行。 我们就给工具换成get请求 在没有c…

JS数组方法map 和 forEach 的区别

一、定义&#xff1a; forEach(): 针对每一个元素执行提供的函数。 map(): 创建一个新的数组&#xff0c;其中每一个元素由调用数组中的每一个元素执行提供的函数得来。 二、区别 1、map 方法返回一个新的数组&#xff0c;而 forEach 方法不会返回任何值&#xff0c;仅仅是遍…

常见的作物模型有哪些?DSSAT模型、APSIM模型、WOFOST模型与PCSE模型等应用

目录 ①最新DSSAT作物模型建模方法及应用 ②基于Python语言快速批量运行DSSAT模型及交叉融合、扩展应用 ③R语言与作物模型&#xff08;以DSSAT模型为例&#xff09;融合应用 ④WOFOST模型与PCSE模型应用 ⑤基于R语言APSIM模型进阶应用与参数优化、批量模拟 ⑥遥感数据与…

十二、【修复工具组】

文章目录 污点修复画笔工具移除工具修复画笔工具修补工具内容感知移动工具红眼工具 污点修复画笔工具 提前说一下&#xff0c;如果光标是十字型的话&#xff0c;可以按一下大写键位&#xff0c;然后光标会变成画笔的圆形状态 污点修复画笔工具是根据画笔选区周围的颜色来进行…

品牌投放小红书种草笔记没有流量怎么办?

品牌做小红书种草投放&#xff0c;流量越大&#xff0c;种草笔记的转化就会越高&#xff0c;所以当你的小红书种草笔记没有流量的时候就要好好思索下是怎么回事&#xff0c;接下来伯乐网络传媒就来给大家分析问题&#xff0c;并提出针对性的解决方案。纯干货&#xff0c;建议收…

maven 编译.../maven-metadata.xml 报错

文章目录 问题解决 问题 突然编译报错: 解决 打开maven的里离线工作模式,感觉就是下载包到本地. 一个是在maven设置里面 或者直接在maven编译的窗口: