JakeWharton-DiskLruCache 源码解释_jakewharton/disklrucache-程序员宅基地

技术标签: android  缓存  源码  Android  Cache  

库地址:https://github.com/JakeWharton/DiskLruCache

简介:DiskLruCache一个硬盘缓存管理工具,为了保持原有空间的大小,其算法是最近最少使用算法。它把最近使用的对象用“强引用”存储在LinkedHashMap.并且把最近最少使用的对象在缓存值达到预设定值之前就从磁盘中移除

其构造函数如下:

private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
    this.directory = directory;//缓存路径
    this.appVersion = appVersion;//app版本
    this.journalFile = new File(directory, JOURNAL_FILE);//日志路径
    this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
    this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
    this.valueCount = valueCount;//每个key对应的资源数
    this.maxSize = maxSize;//最大缓存值
  }

三个文件描述文件操作日志:

static final String JOURNAL_FILE = "journal"; //操作日志
static final String JOURNAL_FILE_TEMP = "journal.tmp";//操作日志缓存
static final String JOURNAL_FILE_BACKUP = "journal.bkp";//操作日志备份

用LinkedHashMap描述 资源对应关系

 private final LinkedHashMap<String, Entry> lruEntries =
  new LinkedHashMap<String, Entry>(0, 0.75f, true);

Entry对象描述 资源流入口,可根据key获得映射Entry

  private final class Entry {
    private final String key;

    /** Lengths of this entry's files. */
    private final long[] lengths;

    /** True if this entry has ever been published. */
    private boolean readable;

    /** The ongoing edit or null if this entry is not being edited. */
    private Editor currentEditor;

    /** The sequence number of the most recently committed edit to this entry. */
    private long sequenceNumber;

    private Entry(String key) {
      this.key = key;
      this.lengths = new long[valueCount];
    }
...
}

每个Entry有对应的Editor 负责资源的流的处理

 public final class Editor {
 private final Entry entry;
 private final boolean[] written;
 private boolean hasErrors;
 private boolean committed;

private Editor(Entry entry) {
  this.entry = entry;
  this.written = (entry.readable) ? null : new boolean[valueCount];
}
...
}

每个Entry 对应多个value资源流,资源流个数由DiskLruCache构造函数参数valueCount 决定,资源名为:

private final class Entry{
...
     public File getCleanFile(int i) {
      return new File(directory, key + "." + i);
    }

    public File getDirtyFile(int i) {
      return new File(directory, key + "." + i + ".tmp");
    }
}

Editor 写流 通过 newOutputStream(int index)方法 写数据到Entry的getDirtyFile(int index)文件,此时为缓存文件,必须调用commit()方法之后,才算真正存储

public final class Editor {
     public OutputStream newOutputStream(int index) throws IOException {
          if (index < 0 || index >= valueCount) {
            throw new IllegalArgumentException("Expected index " + index + " to "
                    + "be greater than 0 and less than the maximum value count "
                    + "of " + valueCount);
          }
          synchronized (DiskLruCache.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);
          }
     }

     public void commit() throws IOException {
      if (hasErrors) {
        completeEdit(this, false);
        remove(entry.key); // The previous entry is stale.
      } else {
        completeEdit(this, true);
      }
      committed = true;
    }
...
}

commit()方法中调用completeEdit()完成资源文件重命名(缓存转实际),此时如果缓存小已超过指定值那么将会调用线程池中开启线程调用trimToSize();

private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
...
    for (int i = 0; i < valueCount; i++) {
      File dirty = entry.getDirtyFile(i);
      if (success) {
        if (dirty.exists()) {
          File clean = entry.getCleanFile(i);
          dirty.renameTo(clean);
          long oldLength = entry.lengths[i];
          long newLength = clean.length();
          entry.lengths[i] = newLength;
          size = size - oldLength + newLength;
        }
      } else {
        deleteIfExists(dirty);
      }
    }
...
    if (size > maxSize || journalRebuildRequired()) {
      executorService.submit(cleanupCallable);
    }
}

trimToSize会从LinkedHashMap中得到需要移除资源的信息,然后remove(); remove删除资源文件,并重新计算size;

private void trimToSize() throws IOException {
    while (size > maxSize) {
      Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
      remove(toEvict.getKey());
    }
 }

public synchronized boolean remove(String key) throws IOException {
    checkNotClosed();
    validateKey(key);
    Entry entry = lruEntries.get(key);
    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(REMOVE + ' ' + key + '\n');
    lruEntries.remove(key);

    if (journalRebuildRequired()) {
      executorService.submit(cleanupCallable);
    }

    return true;
  }

Editor 读流 通过 newInputStream(int index)方法 从对应Entry的getCleanFile(int index)读文件数据

public final class Editor {
    public InputStream newInputStream(int index) throws IOException {
      synchronized (DiskLruCache.this) {
        if (entry.currentEditor != this) {
          throw new IllegalStateException();
        }
        if (!entry.readable) {
          return null;
        }
        try {
          return new FileInputStream(entry.getCleanFile(index));
        } catch (FileNotFoundException e) {
          return null;
        }
      }
    }


}

另外说明一点:缓存size的计算由各个file的length()方法得到字节数累加而成.根据文件操作的不同,size要跟随变化

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Jczmc/article/details/68927575

智能推荐

三分钟教你读懂支票是什么_支票的原理是什么-程序员宅基地

文章浏览阅读8.7k次。三分钟教你读懂支票是什么支票1、支票的概念及特点支票:出票人签发的,委托办理支票存款业务的银行或其他金融机构在见票时无条件支付确定金额给收款人或持票人的票据。支票必填项:支票字样、确定的金额、出票日期、无条件支付委托、付款人名称、出票人签章。支票选填项:付款地、出票地。支票结算特点:(1)简便,手续_支票的原理是什么

山东工商学院 计算机科学与技术,实验中心-山东工商学院计算机科学与技术学院...-程序员宅基地

文章浏览阅读148次。计算机教学实验中心成立于1999年,隶属计算机科学与技术学院。实验中心现有软件、电子、网络、通信、大学生科技创新、AR技术研究所等41间实验室,实验面积5600平方米,固定资产3500万元,教(职)工26人。实验中心以先进精良的设备条件、整洁舒适的教学环境、科学严谨的管理方式为计算机科学与技术学院、信息与电子工程学院、管理科学与工程学院等学院的实验教学、课程设计、毕业设计等实践环节和全院计算机公共..._计算机科学与技术实验教学中心 山东

CUDA ERROR: device-side assert triggered at 问题及解决思路-程序员宅基地

文章浏览阅读10w+次,点赞45次,收藏82次。cuda errorRuntimeError: cuda runtime error (59) : device-side assert triggered at ...我之前还以为是因为GPU抽风了引发的BUG,所以第一次没有在意,直接又重新开始运行了一次,但是第二次就发现程序在同样的地方断掉了,这也就想起来我以前看到的一个博客,里面有句话的大概意思是这样的:每次都在同样的地方出错的..._cuda error: device-side assert triggered

HDOJ1556 树状数组简单应用_hdoj 树状数组基础-程序员宅基地

文章浏览阅读251次。Color the ballTime Limit: 9000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 23142 Accepted Submission(s): 11237Problem DescriptionN个气球排成一排,_hdoj 树状数组基础

浅析HTML_解析html-程序员宅基地

文章浏览阅读430次。HTML面试题整理以及一些自我理解_解析html

idea 启动报错: Failed to create JVM.JVM.Path XXXXXXX\jbr\ 我的解决办法-程序员宅基地

文章浏览阅读2.6k次,点赞2次,收藏6次。idea 启动报错: Failed to create JVM.JVM.Path XXXXXXX\jbr\ 我的解决办法在C:\Users\Administrator\AppData\Roaming\JetBrains\PhpStorm2021.1的文件夹里,找到 phpstorm64.exe.vmoptions 这个文件,并改回原来的配置即可。因为这个路径下的vmoptions文件是我当时修改过的(Help->Edit custom VM Options) phpstorm64.exe.vmop_failed to create jvm

随便推点

Java Swing 如何使用JTree(1)_swing 如何初始化一个jtree-程序员宅基地

文章浏览阅读3k次,点赞2次,收藏6次。Java Swing 如何使用JTree_swing 如何初始化一个jtree

Go 语言到底适合干什么?_对java开发者来说 go 能做什么-程序员宅基地

文章浏览阅读115次。Go 语言对于构建微服务和 API 很有优势,因为它的代码结构简单,易于维护,同时具有高效和高并发特性。由于云计算中的系统通常是分布式的,Go 语言的并发编程模型非常适合用于处理云计算中的任务。由于 Go 语言具有高效和高性能的特性,因此它非常适合用于构建 Web 应用程序。Go 语言原生支持高效的并发编程,因此非常适合用于构建网络应用程序和分布式系统。Go 语言具有高效的执行性能和并发处理能力,因此很适合用于处理大量数据。Go 语言的语言特性、安全性和执行效率都非常适合用于系统编程领域。_对java开发者来说 go 能做什么

【虚拟仿真】Unity3D中拆分模型教程(多种类型模型拆分)_unity怎么拆分模型-程序员宅基地

文章浏览阅读1.4w次,点赞63次,收藏160次。推荐阅读CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客QQ群:1040082875大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。一、前言今天有小伙伴在我这篇文章【虚拟仿真】Unity3D对物体进行拆分实现下面问我如何一秒一拆:虽然我已经给出了思路,但是离实现还是有点思路,正好我对于我这篇文章也是不满意,就解答一下小伙伴的疑惑,然后再将文章内容进行升级。原文章:【虚拟仿真】Unity3D对物体进行拆分实._unity怎么拆分模型

ChatGLM2本地部署的实战方案-程序员宅基地

文章浏览阅读10w+次,点赞55次,收藏74次。本文主要介绍了ChatGLM2本地部署应用的实战方案,希望对学习大语言模型的同学们有所帮助。文章目录1. 介绍2. 配置环境 2.1 安装虚拟环境 2.2 安装依赖库3. 下载权重文件4. 运行ChatGLM2 4.1 方式一 4.2 方式二_chatglm2

r语言c1,R语言之主成分分析-程序员宅基地

文章浏览阅读568次。主成分分析R软件实现程序(一):>d=read.table("clipboard",header=T)#从剪贴板读取数据>sd=scale(d)#对数据进行标准化处理>sd#输出标准化后的数据和属性信息,把标准化的数据拷贝到剪贴板备用>d=read.table("clipboard",header=T)#从剪贴板读取标准化数据>pca=princomp(d,co..._r语言dcor什么意思

webGl学习-程序员宅基地

文章浏览阅读127次。开个新坑,不知道能不能做完学习地址:mdn地址浏览器支持范围:支持范围首先是创建一个容器,与canvas的canvas.getContext('2d')相似let canvas = document.getElementById('myCanvas');let gl = canvas.getContext('webgl');_webgl学习