当前位置: 首页 > news >正文

【Android】LRU 与 Android 缓存策略

LRU 基本实现

LRU 缓存机制是一种常用的缓存策略,其选择最近最久未使用的条目予以淘汰。该算法赋予每个条目一个访问字段 key,用来记录该条目自上次被访问以来所经历的时间 t,当须淘汰某条目时,选择现有条目中其 t 值最大的,即最近最少使用的条目予以淘汰,不关心 t 具体值的情况可以通过链表的顺序插入来实现自然排序,访问字段和条目组成键值对。

LRU 的增改查操作往往要求在常数时间复杂度内进行,要实现顺序排列且在常数时间内删除过期条目,LRU 结构单元可以选择为双向链表,get 查找方法也要在常数时间内完成,所以 LRU 结构构成还要有哈希表的参与,基本的 LRU 结构是:

class Node<K, V>{K key;V value;Node<K, V> prev, next;
}int capacity;
Node<K, V> dummy;
Map<K, Node<K, V>> hm = new HashMap<>();
public int get(int key) {Node result = hm.getOrDefault(key, dummy);if (result != dummy) {moveToHead(result);}return result.value;
}public void put(int key, int value) {Node result = hm.get(key);if (result != null) {moveToHead(result);result.value = value;} else {result = new Node(key, value);result.prev = dummy;result.next = dummy.next;dummy.next.prev = result;dummy.next = result;hm.put(key, result);}if (hm.size() > capacity) {Node del = dummy.prev;del.prev.next = dummy;dummy.prev = del.prev;hm.remove(del.key);}
}public void moveToHead(Node result) {result.prev.next = result.next;result.next.prev = result.prev;result.prev = dummy;result.next = dummy.next;dummy.next.prev = result;dummy.next = result;
}

LruCache

LruCache<K, V> 作为泛型类,内部采用 LinkedHashMap 存储外界缓存对象,通过 get 和 put 方法完成缓存获取和添加操作,初始化过程要提供缓存的总容量大小和重写 sizeOf 方法,sizeOf 方法用来计算缓存对象的大小。

int maxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
/* 缓存总容量大小为当前进程可以用内存的 1/8 */
LruCache<String, Bitmap> cache = new LruCache<>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap bitmap) {return bitmap.getRowBytes() * bitmap.getHeight() / 1024;}
}cache.get(key);
cache.put(key, bitmap);

源码分析

首先来到 get 方法的执行过程,因为 LinkedHashMap 本身线程不安全,所以使用 synchronized 关键字做同步查询,如果有值则 hitCount 命中值自增且就地返回该值,若没有值则 missCount 自增。

if (key == null) {throw new NullPointerException("key == null");
}V mapValue;
synchronized (this) {mapValue = map.get(key);if (mapValue != null) {hitCount++;return mapValue;}missCount++;
}

若没有值则尝试通过 create 方法获得值,该方法默认返回 null,可以重写 create 方法在查询不到值的时候做加载,是懒加载策略的体现,接下来同步尝试将加载的 value 写入哈希表,最后会判断是否有覆写行为,如果有则回调 entryRemoved 方法,该方法系回调监听器,供开发者对旧值做资源释放等操作;若无则调用 trimToSize 方法尝试裁剪超出容量的旧条目。

 /*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/V createdValue = create(key);
if (createdValue == null) {return null;
}synchronized (this) {createCount++;mapValue = map.put(key, createdValue);if (mapValue != null) {// There was a conflict so undo that last put
map.put(key, mapValue);} else {size += safeSizeOf(key, createdValue);}
}if (mapValue != null) {entryRemoved(false, key, createdValue, mapValue);return mapValue;
} else {trimToSize(maxSize);return createdValue;
}

我们来到 trimToSize 方法,其实现比较简单,首先会检查当前容量 size 是否大于 maxSize,是则调用 eldest 方法获得 LinkedHashMap 表尾的键值对条目,接下来从哈希表中删除该条目并更新缓存大小,最后统计被淘汰的条目数量,触发 entryRemoved 回调。

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) {break;}Map.Entry<K, V> toEvict = eldest();if (toEvict == null) {break;}key = toEvict.getKey();value = toEvict.getValue();map.remove(key);size -= safeSizeOf(key, value);evictionCount++;}entryRemoved(true, key, value, null);}
}

DiskLruCache

DiskLruCache 用于实现存储设备缓存,通过将缓存对象写入文件系统实现缓存效果,该类不属于 Android SDK 的内容,所以要引入依赖:implementation 'com.jakewharton:disklrucache:2.0.2',创建方式是通过 open 方法,其参数 directory 表示期望磁盘缓存在文件系统的缓存路径,appVersion 类似 DatabaseHelper 的 update 机制,appVersion 发生改变时会清空之前所有的缓存文件,valueCount 表示单个节点对应的数据个数,maxSize 表示缓存的总大小。

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {diskCacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);

缓存写入操作主要通过 Editor 对象完成。Editor 表示缓存条目的可编辑视图,可以通过 DiskLruCache 实例的 edit(key) 方法获取对应的 Editor 实例。通过该实例可以获取输出流,向缓存写入数据。写入操作产生的所有字节数据对应创建 Editor 时指定的 key 值。若要删除缓存条目,可以调用 remove(key) 方法将其清除。值得注意的是,DiskLruCache 不允许同时编辑相同的缓存对象,如果该缓存正在被编辑,则 edit 会返回 null。

String key = "bitmap_key";
DiskLruCache.Editor editor = diskLruCache.edit(key);
if (editor != null) {OutputStream os = editor.newOutputStream(0);try {os.write("Hello DiskLruCache".getBytes());editor.commit();} catch (IOException e) {editor.abort(); // 回退操作e.printStackTrace();} finally {try { os.close(); } catch (IOException ignored) {}}
}

缓存查找要借助表示缓存条目只读视图的 Snapshot 类,逻辑和 Editor 类似,可以通过 DiskLruCache 实例的 get(key) 方法获取对应的 Snapshot 实例。通过该实例可以获取输入流,读取缓存中的数据。读取操作不会修改缓存条目,获取的数据对应创建时指定的 key 值。使用完成后需要调用 snapshot.close() 关闭流,释放资源。

DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
if (snapshot != null) {InputStream is = snapshot.getInputStream(0);BufferedReader reader = new BufferedReader(new InputStreamReader(is));StringBuilder sb = new StringBuilder();String line;while ((line = reader.readLine()) != null) {sb.append(line);}String cachedValue = sb.toString();snapshot.close();Log.d("DiskLruCache", "Cached value: " + cachedValue);
}

Bitmap

Bitmap 在 Android 指 1 幅图片资源,可以通过 BitmapFactory 的 decodeFile、decodeResource、decodeStream 和 decodeByteArray 方法分别从文件系统、资源、输入流和字节数组加载 Bitmap 对象,最终会调用 BitmapFactory 的 native 层方法。

大多数情况 ImageView 显示图片的内容区域要小于图片本身的大小,所以将图片完整的加载到设备再使用 scaleType 属性进行缩放或裁切往往是不高效的,我们可以采用 BitmapFactory.Options 来加载适应尺寸的图片,Options 有 inSampleSize 参数表示采样率,inSampleSize 会重复作用于 Bitmap 的宽高,所以缩放比例是 1/inSampleSize^2。

Options 的 inJustDecodeBounds 参数表示是否仅获取图片信息,所以我们可以通过获取图片宽高信息后结合 ImageView 显示区域的尺寸计算出采样率 inSampleSize,使用该采样率加载 Bitmap。

public static Bitmap suitableBitmap(Resources res, int resId, int weith, int height) {final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(res, resId, options);options.inSampleSize = calculateInSampleSize(options, weith, height);/* calculateInSampleSize 获得采样率过程略 */options.inJustDecodeBounds = false;return BitmapFactory.decodeResource(res, resId, options);
}
http://www.dtcms.com/a/618199.html

相关文章:

  • 使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 26--数据驱动--参数化处理 Excel 文件 3
  • 第41节:第三阶段总结:打造一个AR家具摆放应用
  • 建设网站流程2022年最新新闻播报稿件
  • 网站地图的作用长沙网站开发设计
  • 【读代码】最新端侧TTS模型NeuTTS-Air
  • 做装修网站多少钱四川成都住建局官网
  • Microsoft 远程桌面app,支持挂机宝,云主机服务器
  • 基于MATLAB的粒子群优化(PSO)算法对25杆桁架结构进行优化设计
  • 智能驾驶:从感知到规控的自动驾驶系统全解析
  • 练习项目:基于 LangGraph 和 MCP 服务器的本地语音助手
  • 在 VMware 的 Ubuntu 22.04 虚拟机和 Windows 主机之间设置共享剪贴板
  • 淄博专业网站建设哪家专业公司装修设计工程
  • 金融网站的设计中和阗盛工程建设有限公司网站
  • 《JavaScript基础-Day.4》笔记总结
  • 关于C++中的预编译指令
  • 做网站的重要性深圳程序开发
  • 其他落地手册:facebook实现与音视频剖析
  • 建站方法移动课程播放网站建设多少钱
  • ZJUCTF2025(预赛+决赛)-我的writeup
  • 2025.11.16 AI快讯
  • Java分治算法题目练习(快速/归并排序)
  • Python 生信进阶:Biopython 库完全指南(序列处理 + 数据库交互)
  • 基于单片机的功率因数校正与无功补偿系统设计
  • 【计算机网络笔记】第六章 数据链路层
  • 网站开发工作前景电商哪个平台销量最好
  • 正规的网站建设官网动漫设计难不难
  • 运行,暂停,检查:探索如何使用LLDB进行有效调试
  • YOLOv8交通信号灯检测
  • asp.net企业网站管理系统工厂型企业做网站
  • linux gpib 驱动