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要点--不可不用的磁盘缓存工具类相关推荐

  1. 缓存工具类MyCacheUtil

    MyCacheUtil.java package com.sunrise.jop.common.util;import java.io.File; import java.sql.Timestamp; ...

  2. Java 缓存工具类 Cache

    Java 缓存工具类 Cache 工具类定义 工具类定义 package com.demo.utils;import org.springframework.util.StringUtils;impo ...

  3. android 图片缓存工具类,Android工具类系列-Glide图片缓存与圆角

    Glide的图片缓存和清除图片缓存 public class GlideCacheUtil { private static GlideCacheUtil inst; public static Gl ...

  4. java redis缓存工具类_redis工具类-JedisUtil

    redis连接的工具类 1.java中的redis java中,使用redis不会将其当作数据库来使用,更多的是作为缓存或者是消息中间件来使用.在用作缓存时,我们需要使用第三方提供的jar包来进行开发 ...

  5. TimedCache 带时间缓存工具类,附加监听回调 | Java工具类

    目录 简述 Maven依赖 简单使用 工具优化-监听过期.增加回调 总结 简述 我们在工作中会碰到需要使用带过期时间的缓存场景.但是使用redis有太重了,毕竟缓存的数据很小,放在内存够够的.huto ...

  6. Redis存储缓存工具类简单封装

    一.公共实体类 (1)User.java package cn.xiyou.entity;import java.io.Serializable;/*** User实体* * @author XIOA ...

  7. js 浏览器缓存 手写缓存工具类

    浏览器缓存 数据存储在浏览器内存中,不会被页面刷新消失,存储量有大小限制,不过实在开发中,合理的应用已经可以满足绝大缓存需求 sessionStorage 会话级别 关闭窗口则自动清除 localSt ...

  8. java redis缓存工具类_util: 对日常工作中常见的工具类进行打包: 1、数据库:DBUtil工具类 2、Redis缓存:JedisUtil工具类...

    /** * 功能:使用原始SQL查询单条记录 * 返回每条记录封装到Map */ public static void demo1_1() { DBUtil db = new DBUtil(); tr ...

  9. 【工具篇】使用concurrentHashMap实现缓存工具类

最新文章

  1. qt布局中listwidget 保持固定宽度_UI设计中响应式设计实用技巧
  2. python面向对象大段代码_python面向对象部分简单整理
  3. OVS搭建虚拟机全过程(四十五)
  4. 【转】每天一个linux命令(39):grep 命令
  5. 笔记本x31搭建家用win服务器系统,Thinkpad X31怎么硬盘安装win7系统
  6. 中震弹性计算_众值烈度、中震烈度、大震烈度及三水准二阶段
  7. 数仓dw怎么建_搭建数据仓库的流程简介
  8. hdu 4293 Groups DP
  9. python面试题总结(一)字符串反转,写取指定数函数
  10. Docker中级篇|深入探究Docker
  11. java kafka 拉取_java获取kafka consumer lag
  12. Django admin 继承user表后密码为明文,继承UserAdmin,重写其方法
  13. Shiro的多Realm验证的实现--shiro实现不同身份使用不同Realm进行验证
  14. Win7升Windows10有获取通知,但是就不推送的解决方法
  15. 【Python实例第28讲】核主成分
  16. 【AI视野·今日CV 计算机视觉论文速览 第193期】Fri, 7 May 2021
  17. 计算机英语截短词,英语词汇构词法(Word Formation)——截短法
  18. TMS570快速上手指南(0)--概述
  19. Unity在UI上使用MaskableGraphic类画一个矩形刻度尺
  20. 2021年全国压岁钱排行榜,大数据告诉你哪的红包最大?

热门文章

  1. 注册登录案例用MVC和mysql_用MVC模式实现简单用户登录注册功能
  2. Image Watch的使用示例
  3. minui 向div放html,c# – 如何添加css类到html通用控件div?
  4. pyqt5 qscrollarea到达_在PYQT5中QscrollArea(滚动条)的使用方法
  5. 分布式系统唯一ID生成方案汇总
  6. vue下使用 mint-ui,修改主题样式为微信UI的绿色风格
  7. ios中一个开发者证书如何创建多个app应用
  8. 使用 trait 时报PHP Parse error: syntax error, unexpected 'use' (T_USE) 这个错误
  9. MySQL 行子查询
  10. sendmail邮件服务器支持账户名大小写