LruDiskCache要点--不可不用的磁盘缓存工具类
LruDiskCache是使用Lru算法的磁盘缓存类,它的功能是将LruCache中缓存位置由内存改为磁盘,一般两者结合使用,用于对处理小文件,图片的缓存。
下面记录下阅读过程中几个比较重要的点:
Get
获取缓存数据时,LruDiskCache会使用LinkedHashmap的算法,也就是最常使用的放在尾部,最少使用的首先被遍历到.
当你需要获取缓存数据时,首先会得到是一个Snapshot对象(如果数据正常的话:写入成功、在有效内等等),Snapshot其实就是持有缓存文件的输入流,无其它逻辑操作。
private synchronized Snapshot getByDiskKey(String diskKey) throws IOException {checkNotClosed();Entry entry = lruEntries.get(diskKey);if (entry == null) {return null;}if (!entry.readable) {//数据是否被写入成功return null;}// 判断时间有效性if (entry.expiryTimestamp < System.currentTimeMillis()) {for (int i = 0; i < valueCount; i++) {File file = entry.getCleanFile(i);if (file.exists() && !file.delete()) {throw new IOException("failed to delete " + file);}size -= entry.lengths[i];entry.lengths[i] = 0;}redundantOpCount++;journalWriter.append(DELETE + " " + diskKey + '\n');lruEntries.remove(diskKey);if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return null;}//同一个key可能对应多个缓存FileInputStream[] ins = new FileInputStream[valueCount];try {for (int i = 0; i < valueCount; i++) {ins[i] = new FileInputStream(entry.getCleanFile(i));}} catch (FileNotFoundException e) {// A file must have been deleted manually!for (int i = 0; i < valueCount; i++) {if (ins[i] != null) {IOUtils.closeQuietly(ins[i]);} else {break;}}return null;}redundantOpCount++;journalWriter.append(READ + " " + diskKey + '\n');if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return new Snapshot(diskKey, entry.sequenceNumber, ins, entry.lengths);}
Set
增加缓存数据时,需先调用edit方法,获得Editor对象,或者null(已经是edit状态时),并写入一条update日志,该条日志并不是写入缓存成功的标识。
注:diskKey为原key经过md5加密后的值。
private synchronized Editor editByDiskKey(String diskKey, long expectedSequenceNumber) throws IOException {checkNotClosed();Entry entry = lruEntries.get(diskKey);if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER &&(entry == null || entry.sequenceNumber != expectedSequenceNumber)) {return null; // Snapshot is stale.}if (entry == null) {entry = new Entry(diskKey);lruEntries.put(diskKey, entry);} else if (entry.currentEditor != null) {return null; // Another edit is in progress.}Editor editor = new Editor(entry);entry.currentEditor = editor;// Flush the journal before creating files to prevent file leaks.journalWriter.write(UPDATE + " " + diskKey + '\n');journalWriter.flush();return editor;}
然后通过Editor对象获得文件的操作流FaultHidingOutputStream对象,该对象在操作文件出错的时候会将hasErrors变量赋值为false,该变量对最终插入数据成功与否起关键性的作用。
代码中entry.getDirtyFile(index),可能有同学有疑问,为什么是Dirty。其实这里只是作为一个临时文件,在数据写入成功后会将改文件重命名后做为正式文件。
readable表示当前Entry的数据是否被写入过了,如果是,则不能再重复写入。
public OutputStream newOutputStream(int index) throws IOException {synchronized (LruDiskCache.this) {if (entry.currentEditor != this) {throw new IllegalStateException();}if (!entry.readable) {written[index] = true;}File dirtyFile = entry.getDirtyFile(index);FileOutputStream outputStream;try {outputStream = new FileOutputStream(dirtyFile);} catch (FileNotFoundException e) {// Attempt to recreate the cache directory.directory.mkdirs();try {outputStream = new FileOutputStream(dirtyFile);} catch (FileNotFoundException e2) {// We are unable to recover. Silently eat the writes.return NULL_OUTPUT_STREAM;}}return new FaultHidingOutputStream(outputStream);}}
最终新增加缓存,需调用Editor对象的commit方法;
在commit时,会进行判断,如果写入成功,刚加一条clean日志(clean才表示数据插入成功了)
1
2
|
entry.readable = true ;
journalWriter.write(CLEAN + " " + entry.diskKey + " " + EXPIRY_PREFIX + entry.expiryTimestamp + entry.getLengths() + '\n' );
|
否则做为脏数据处理,删除文件并写入删除日志。
1
|
journalWriter.write(DELETE + " " + entry.diskKey + '\n' );
|
Delete
删除缓存时,先判断是不是edit状态,不是才能执行删除操作,并将删除记录写入日志文件。
如果同一key对应多个缓存文件,则全删。
private synchronized boolean removeByDiskKey(String diskKey) throws IOException {checkNotClosed();Entry entry = lruEntries.get(diskKey);if (entry == null || entry.currentEditor != null) {return false;}for (int i = 0; i < valueCount; i++) {File file = entry.getCleanFile(i);if (file.exists() && !file.delete()) {throw new IOException("failed to delete " + file);}size -= entry.lengths[i];entry.lengths[i] = 0;}redundantOpCount++;journalWriter.append(DELETE + " " + diskKey + '\n');lruEntries.remove(diskKey);if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return true;}
其它分析:
许多操作的结尾处都会添加一个整理数据的任务,保证数据在可控范围:
private final Callable<Void> cleanupCallable = new Callable<Void>() {public Void call() throws Exception {synchronized (LruDiskCache.this) {if (journalWriter == null) {return null; // Closed.}trimToSize();//超过大小时删除不常用数据if (journalRebuildRequired()) {//超过2000条日志后需要重建日志rebuildJournal();//根据现在的数据生成新的日志文件,重命名原有的日志文件redundantOpCount = 0;//初始化}}return null;}};
日志文件是有限制长度的,不能随意增长:
private boolean journalRebuildRequired() {final int redundantOpCompactThreshold = 2000;return redundantOpCount >= redundantOpCompactThreshold //&& redundantOpCount >= lruEntries.size();}
有些操作会遍历valueCount,这个值表示同一个key下的多个缓存,比如一张图片可分为大中小,这时候就以valueCount来做区分.
(上述代码来源于xutils中的LruDiskCache,相对于Android官网内的DiskLruCache,它显然更加完善。)
LruDiskCache要点--不可不用的磁盘缓存工具类相关推荐
- 缓存工具类MyCacheUtil
MyCacheUtil.java package com.sunrise.jop.common.util;import java.io.File; import java.sql.Timestamp; ...
- Java 缓存工具类 Cache
Java 缓存工具类 Cache 工具类定义 工具类定义 package com.demo.utils;import org.springframework.util.StringUtils;impo ...
- android 图片缓存工具类,Android工具类系列-Glide图片缓存与圆角
Glide的图片缓存和清除图片缓存 public class GlideCacheUtil { private static GlideCacheUtil inst; public static Gl ...
- java redis缓存工具类_redis工具类-JedisUtil
redis连接的工具类 1.java中的redis java中,使用redis不会将其当作数据库来使用,更多的是作为缓存或者是消息中间件来使用.在用作缓存时,我们需要使用第三方提供的jar包来进行开发 ...
- TimedCache 带时间缓存工具类,附加监听回调 | Java工具类
目录 简述 Maven依赖 简单使用 工具优化-监听过期.增加回调 总结 简述 我们在工作中会碰到需要使用带过期时间的缓存场景.但是使用redis有太重了,毕竟缓存的数据很小,放在内存够够的.huto ...
- Redis存储缓存工具类简单封装
一.公共实体类 (1)User.java package cn.xiyou.entity;import java.io.Serializable;/*** User实体* * @author XIOA ...
- js 浏览器缓存 手写缓存工具类
浏览器缓存 数据存储在浏览器内存中,不会被页面刷新消失,存储量有大小限制,不过实在开发中,合理的应用已经可以满足绝大缓存需求 sessionStorage 会话级别 关闭窗口则自动清除 localSt ...
- java redis缓存工具类_util: 对日常工作中常见的工具类进行打包:
1、数据库:DBUtil工具类
2、Redis缓存:JedisUtil工具类...
/** * 功能:使用原始SQL查询单条记录 * 返回每条记录封装到Map */ public static void demo1_1() { DBUtil db = new DBUtil(); tr ...
- 【工具篇】使用concurrentHashMap实现缓存工具类
最新文章
- qt布局中listwidget 保持固定宽度_UI设计中响应式设计实用技巧
- python面向对象大段代码_python面向对象部分简单整理
- OVS搭建虚拟机全过程(四十五)
- 【转】每天一个linux命令(39):grep 命令
- 笔记本x31搭建家用win服务器系统,Thinkpad X31怎么硬盘安装win7系统
- 中震弹性计算_众值烈度、中震烈度、大震烈度及三水准二阶段
- 数仓dw怎么建_搭建数据仓库的流程简介
- hdu 4293 Groups DP
- python面试题总结(一)字符串反转,写取指定数函数
- Docker中级篇|深入探究Docker
- java kafka 拉取_java获取kafka consumer lag
- Django admin 继承user表后密码为明文,继承UserAdmin,重写其方法
- Shiro的多Realm验证的实现--shiro实现不同身份使用不同Realm进行验证
- Win7升Windows10有获取通知,但是就不推送的解决方法
- 【Python实例第28讲】核主成分
- 【AI视野·今日CV 计算机视觉论文速览 第193期】Fri, 7 May 2021
- 计算机英语截短词,英语词汇构词法(Word Formation)——截短法
- TMS570快速上手指南(0)--概述
- Unity在UI上使用MaskableGraphic类画一个矩形刻度尺
- 2021年全国压岁钱排行榜,大数据告诉你哪的红包最大?
热门文章
- 注册登录案例用MVC和mysql_用MVC模式实现简单用户登录注册功能
- Image Watch的使用示例
- minui 向div放html,c# – 如何添加css类到html通用控件div?
- pyqt5 qscrollarea到达_在PYQT5中QscrollArea(滚动条)的使用方法
- 分布式系统唯一ID生成方案汇总
- vue下使用 mint-ui,修改主题样式为微信UI的绿色风格
- ios中一个开发者证书如何创建多个app应用
- 使用 trait 时报PHP Parse error: syntax error, unexpected 'use' (T_USE) 这个错误
- MySQL 行子查询
- sendmail邮件服务器支持账户名大小写