DiskLruCache 描述:

DiskLruCache 是用来缓存一些数据,比如网络访问的Json,加载的图片等等。 LruCache
是把数据缓存到内存中,而DisLruCache 是把数据缓存到设备里面。DiskLruCache 使用和LruCache 的一样的设计思想。

参考LruCahce 的地址:https://blog.csdn.net/u013270444/article/details/104852681

DiskLruCache类的职责:

  1. 负责维护缓存列表表和缓存文件的对应关系
  2. 负责维护缓存区域的大小,当缓存的内容超过设定的额度后,使用最近最少使用算法,删除不常用的缓存。
  3. 提供缓存文件的存取接口

实现原理分析:

DiskLruCache 有一张journal 的日志文件,记录了所有的缓存的文件。

文件格式如下:

libcore.io.DiskLruCache
1
100
2CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6  832 21054
DIRTY 335c4c6028171cfddfbaae1a9c313c52
CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
REMOVE 335c4c6028171cfddfbaae1a9c313c52
DIRTY 1ab96a171faeeee38496d8b330771a7a
CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
READ 335c4c6028171cfddfbaae1a9c313c52
READ 3400330d1dfc7f3f7f4b8d4d803dfcf6

文件含义如下:

最上面的五行,第一行表示这个是一个DiskLruCache 日志文件,是个字符串没有特别的含义,第二行表示硬盘缓存的版本,第三行表示app
版本号,第四行表示一个文件key 对应几个文件。第五行是一个空行。

每一条数据的含义:

CLEAN: 表示数据是干净的
DIRTY : 表示这条数据是脏的,不会使用这条数据。
REMOVE: 表示这条数据被移除了。
READ: 表示这个数据被读取了一次。

3400330d1dfc7f3f7f4b8d4d803dfcf6 表示当前这条数据的key,当我们调用get 的时候会用到。
后面的832 21054表示这个数据对应的一个或多个文件。前面那个代表第一个文件的大小,第二个表示第二个文件的大小,以此类推。

当我们初始化DiskLruCache 的时候,会读取这个日志文件。把文件里面的数据读取出来。

只有当读取到CLEAN 的条目的时候,缓存才是可用的。
当读取到remove 的时候,会把这条数据从Map 里面移除掉。

相关代码如下:

  private void readJournalLine(String line) throws IOException {int firstSpace = line.indexOf(' ');if (firstSpace == -1) {throw new IOException("unexpected journal line: " + line);}int keyBegin = firstSpace + 1;int secondSpace = line.indexOf(' ', keyBegin);final String key;if (secondSpace == -1) {key = line.substring(keyBegin);if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {//如果是remove 那么移除掉这条数据lruEntries.remove(key);return;}} else {key = line.substring(keyBegin, secondSpace);}Entry entry = lruEntries.get(key);if (entry == null) {entry = new Entry(key);lruEntries.put(key, entry);}if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {String[] parts = line.substring(secondSpace + 1).split(" ");// clean 的数据 才是可读的entry.readable = true;entry.currentEditor = null;entry.setLengths(parts);} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {//dirty 的数据是不可读取的entry.currentEditor = new Editor(entry);} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {// This work was already done by calling lruEntries.get().} else {throw new IOException("unexpected journal line: " + line);}}

关于DIRTY CLEAN REMOVE READ的写入时机

当我们存一条缓存数据到设备上的时候,DiskLruCache 会先写一条Dirty> 的信息到日志里面,当缓存文件成功写入到设备的时候,会写一条Clean 的数据,key 和 dirty 的key
是一样的。但是如果写入文件失败,就会写一条Remove 的数据。当一条缓存数据被读取的时候,会写一条Read 的数据。当我们删除缓存的时候,会有一条Remove 的数据。

作为一个缓存的提供者,那么肯定有存放和读取的操作。我们看下:

存放:

  private synchronized void completeEdit(Editor editor, boolean success) throws IOException {Entry entry = editor.entry;if (entry.readable | success) {entry.readable = true;journalWriter.append(CLEAN);journalWriter.append(' ');journalWriter.append(entry.key);journalWriter.append(entry.getLengths());journalWriter.append('\n');if (success) {entry.sequenceNumber = nextSequenceNumber++;}} else {lruEntries.remove(entry.key);journalWriter.append(REMOVE);journalWriter.append(' ');journalWriter.append(entry.key);journalWriter.append('\n');}flushWriter(journalWriter);if (size > maxSize || journalRebuildRequired()) {executorService.submit(cleanupCallable);}}

如果发现大小超过了最大的缓存大小,那么执行清理的Runnable

  private final Callable<Void> cleanupCallable = new Callable<Void>() {public Void call() throws Exception {synchronized (DiskLruCache.this) {trimToSize();return null;}};
  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();Entry entry = lruEntries.get(key);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);journalWriter.append(' ');journalWriter.append(key);journalWriter.append('\n');lruEntries.remove(key);return true;}

读取:

  public synchronized Value get(String key) throws IOException {checkNotClosed();Entry entry = lruEntries.get(key);if (!entry.readable) {return null;}redundantOpCount++;journalWriter.append(READ);journalWriter.append(' ');journalWriter.append(key);journalWriter.append('\n');if (journalRebuildRequired()) {executorService.submit(cleanupCallable);}return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);}

如上面的代码,每次读取都会写一个Read.

  /** A snapshot of the values for an entry. */public final class Value {private Value(String key, long sequenceNumber, File[] files, long[] lengths) {this.key = key;this.sequenceNumber = sequenceNumber;this.files = files;this.lengths = lengths;}/*** Returns an editor for this snapshot's entry, or null if either the* entry has changed since this snapshot was created or if another edit* is in progress.*/public Editor edit() throws IOException {return DiskLruCache.this.edit(key, sequenceNumber);}public File getFile(int index) {return files[index];}/** Returns the string value for {@code index}. */public String getString(int index) throws IOException {InputStream is = new FileInputStream(files[index]);return inputStreamToString(is);}/** Returns the byte length of the value for {@code index}. */public long getLength(int index) {return lengths[index];}}

DiskLruCache 的使用:

初始化:

    private void initLruCache() {//初始化lru cacheFile file = new File("/sdcard/Lru/");file.mkdir();try {mDiskLruCache = DiskLruCache.open(file,1,1,1024 * 1024 * 250);} catch (IOException e) {e.printStackTrace();}}

存储:

        /*** 注意              mDiskLruCache = DiskLruCache.open(file,1,1,1024 * 1024 * 250);* 如果说valueCount 参数你设置的2个,那么你提交的时候 不能只提交一个 会直接报错*/try {DiskLruCache.Editor edit = mDiskLruCache.edit("1238988383");File file1 = edit.getFile(0);PrintStream fileOutputStream = new PrintStream(new FileOutputStream(file1));fileOutputStream.print("测试测试");fileOutputStream.flush();fileOutputStream.close();edit.commit();} catch (IOException e) {e.printStackTrace();}

读取:

                try {DiskLruCache.Value value = mDiskLruCache.get("1238988383");//判空处理if (value != null) {String string = value.getString(0);Toast.makeText(getActivity(), string, Toast.LENGTH_SHORT).show();}} catch (IOException e) {e.printStackTrace();}

删除

                try {mDiskLruCache.remove("1238988383");//如果没有flush 的话  可能已经删除的item 不会写到journal 里面mDiskLruCache.flush();} catch (IOException e) {e.printStackTrace();}

注意:

如果你写的一个key 对应了两个value,但是如果你只写了一个文件,那么就会报下面的错。

2020-03-30 17:21:46.970 24743-24743/com.pipiyang.cn03 E/AndroidRuntime: FATAL EXCEPTION: mainProcess: com.pipiyang.cn03, PID: 24743java.lang.IllegalStateException: Newly created entry didn't create value for index 1at com.bumptech.glide.disklrucache.DiskLruCache.completeEdit(DiskLruCache.java:518)at com.bumptech.glide.disklrucache.DiskLruCache.access$2100(DiskLruCache.java:90)at com.bumptech.glide.disklrucache.DiskLruCache$Editor.commit(DiskLruCache.java:835)at com.example.fragment.DiskLruCacheFragment.putSomeThing(DiskLruCacheFragment.java:98)at com.example.fragment.DiskLruCacheFragment.onCreateView(DiskLruCacheFragment.java:85)at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2439)at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:802)at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2273)at androidx.fragment.app.FragmentManagerImpl$1.run(FragmentManager.java:733)at android.os.Handler.handleCallback(Handler.java:883)at android.os.Handler.dispatchMessage(Handler.java:100)at android.os.Looper.loop(Looper.java:230)at android.app.ActivityThread.main(ActivityThread.java:7742)at java.lang.reflect.Method.invoke(Native Method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034)

相关截图资料:


日志文件读取到内存中的数据结构是什么?

  final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);private final class Entry {final String key;/** Lengths of this entry's files. */final long[] lengths;final File[] cleanFiles;final File[] dirtyFiles;/** True if this entry has ever been published. */boolean readable;/** The ongoing edit or null if this entry is not being edited. */Editor currentEditor;/** The sequence number of the most recently committed edit to this entry. */long sequenceNumber;}

怎么实现的Lru?

和LruCache 一样,使用的是LinkedHashMap,经常访问的放到链表最后面,移除的时候,移除最不常使用的。

日志文件会不会越来越大?

不会,如果冗余的日志多于2000 条的时候,会进行日志重写。触发时机为打开日志的时候,和往DiskLruCache 存放数据的时候。

关键 变量为redundantOpCount (冗余的日志行数),如果这个变量大于2000,就会重写日志。

  /*** We only rebuild the journal when it will halve the size of the journal and eliminate at least* 2000 ops.*/boolean journalRebuildRequired() {final int redundantOpCompactThreshold = 2000;return redundantOpCount >= redundantOpCompactThreshold&& redundantOpCount >= lruEntries.size();}private final Runnable cleanupRunnable = new Runnable() {public void run() {try {if (journalRebuildRequired()) {rebuildJournal();redundantOpCount = 0;}};

redundantOpCount 改变的地方:


okhttp3.internal.cache.DiskLruCache#readJournal
在我们初始化读取日志的时候 会用文件行数减去我们真实数据的个数   得到当前冗余的数量redundantOpCount = lineCount - lruEntries.size();okhttp3.internal.cache.DiskLruCache#removeEntryredundantOpCount++;
当我们移除一个无用的数据的时候,也会增加redundantOpCount 的个数okhttp3.internal.cache.DiskLruCache#getredundantOpCount++;
因为我们读取到的时候,也会写日志,所以也会增加okhttp3.internal.cache.DiskLruCache#completeEdit
一个文件写完成  也会增加  因为会有两条日志  一条是DIRTY 一条是CLEAN

每次重写日志之后,都没有DIRTY,只有CLEAN.

DiskLruCache 源码解析相关推荐

  1. Android之DiskLruCache源码解析

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/73863258 本文出自:[顾林海的博客] 个人开发的微信小程序,目前功 ...

  2. DiskLruCache源码解析

    DiskLruCache是google开源的一个本地缓存类,虽然没有成为android的库类,但是非常好用,我个人推荐使用这个类进行缓存. 当然也已经有很多人在使用这个类了,但是很多事情,我们不但要知 ...

  3. 彻底理解OkHttp - OkHttp 源码解析及OkHttp的设计思想

    OkHttp 现在统治了Android的网络请求领域,最常用的框架是:Retrofit+okhttp.OkHttp的实现原理和设计思想是必须要了解的,读懂和理解流行的框架也是程序员进阶的必经之路,代码 ...

  4. Android经典著名的百大框架源码解析(retrofit、Okhttp、Glide、Zxing、dagger等等)

    我们Android程序员每天都要和源码打交道.经过数年的学习,大多数程序员可以"写"代码,或者至少是拷贝并修改代码.而且,我们教授编程的方式强调编写代码的艺术,而不是如何阅读代码. ...

  5. Glide 4.9源码解析-缓存策略

    本文Glide源码基于4.9,版本下载地址如下:Glide 4.9 前言 在分析了Glide的图片加载流程后,更加发觉到Glide的强大,于是这篇文章将继续深入分析Glide的缓存策略.不过今天的文章 ...

  6. BAT高级架构师合力熬夜15天,肝出了这份PDF版《Android百大框架源码解析》,还不快快码住。。。

    前言 为什么要阅读源码? 现在中高级Android岗位面试中,对于各种框架的源码都会刨根问底,从而来判断应试者的业务能力边际所在.但是很多开发者习惯直接搬运,对各种框架的源码都没有过深入研究,在面试时 ...

  7. OkHttp 源码解析(4.9.1 版本)

    文章目录 1.OkHttp 简介 2.OkHttp 配置与基本用法 2.1 依赖引入与配置 2.2 基本用法 3.OkHttp 常见对象介绍 4.OkHttp 源码解析 4.1 当我们调用`okhtt ...

  8. Android 图片加载框架Gilde源码解析

    1.使用Gilde显示一张图片 Glide.with(this).load("https://cn.bing.com/sa/simg/hpb/xxx.jpg").into(imag ...

  9. Glide的源码解析(一)(附方法调用图)

    前言 上一篇博客讲了Glide的基本使用,知其然,也要知其所以然,这篇博客一起来学习Glide的源码.如果不知道Glide的基本使用,可以看上一篇博客:http://blog.csdn.net/luo ...

最新文章

  1. html5中text-align,text-align
  2. 环保—北京周边 自行车骑行线路大全
  3. linux添加自己的库,Linux学习笔记——例叙makefile 增加自定义共享库
  4. IJCAI 2018:中科院计算所:增强对话生成一致性的序列到序列模型
  5. golang int64转string_(一)Golang从入门到原地起飞
  6. Java 数组插入元素
  7. thymeleaf的属性优先级
  8. 【白皮书分享】2020智能体白皮书-华为.pdf(附下载链接)
  9. SpringBoot加载静态资源
  10. SCI从入门到精通(二)——如何阅读文献
  11. servlet笔试题java,Java推荐!Servlet面试题和答案汇集
  12. Mac 快捷键 桌面壁纸
  13. Win10设置热点IP
  14. 流程图软件用哪款: Draw.io, 亿图图示, ProcessOn. 做最适合你的流程图
  15. 常见的图标库有哪些?
  16. 全志A33N切换分支.repo/repo/repo forall -c git checkout exdroid-7.1.1_r23-a33-v7.0rc2.1
  17. 微信刷票python代码_微信刷票漏洞详解, Python脚本实现一秒破万!
  18. Echarts示例大全 Demo合集网站
  19. [Windows] 迅雷 无修改 无限制 无视封锁
  20. 【资源分享(免积分)】增长黑客_创业公司的用户与收入增长秘籍 - 范冰(高清版免费).pdf

热门文章

  1. 自学了python基础英语_Python自学路线图之Python基础自学
  2. python列表取出元素_python中的列表,添加元素,获取元素,删除元素,列表分片,常用操作符...
  3. 能力素质有所欠缺_孩子说话啰嗦没重点?家长学会“大脑整理术”,提高孩子表达能力...
  4. nike附近门店查询_门店配送的全国服务网络如何快速成功运营?
  5. C++知识点60——非类型模板参数
  6. 从java到c_Binder机制,从Java到C (4. Parcel)
  7. mysql 错误1930xc1_Mysql写入记录出现 Incorrect string value: '\xB4\xE7\xB1\xCA\xBC\xC7‘错误?(写入中文)...
  8. 华为云AIOps实践全面解析
  9. Mac下编译Android源码,并导入IntelliJ IDEA进行源码阅读
  10. wince 6.0 串口 读取 readfile 超时问题