DiskLruCache 源码解析
DiskLruCache 描述:
DiskLruCache 是用来缓存一些数据,比如网络访问的Json,加载的图片等等。 LruCache
是把数据缓存到内存中,而DisLruCache 是把数据缓存到设备里面。DiskLruCache 使用和LruCache 的一样的设计思想。参考LruCahce 的地址:https://blog.csdn.net/u013270444/article/details/104852681
DiskLruCache类的职责:
- 负责维护缓存列表表和缓存文件的对应关系
- 负责维护缓存区域的大小,当缓存的内容超过设定的额度后,使用最近最少使用算法,删除不常用的缓存。
- 提供缓存文件的存取接口
实现原理分析:
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 源码解析相关推荐
- Android之DiskLruCache源码解析
转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/73863258 本文出自:[顾林海的博客] 个人开发的微信小程序,目前功 ...
- DiskLruCache源码解析
DiskLruCache是google开源的一个本地缓存类,虽然没有成为android的库类,但是非常好用,我个人推荐使用这个类进行缓存. 当然也已经有很多人在使用这个类了,但是很多事情,我们不但要知 ...
- 彻底理解OkHttp - OkHttp 源码解析及OkHttp的设计思想
OkHttp 现在统治了Android的网络请求领域,最常用的框架是:Retrofit+okhttp.OkHttp的实现原理和设计思想是必须要了解的,读懂和理解流行的框架也是程序员进阶的必经之路,代码 ...
- Android经典著名的百大框架源码解析(retrofit、Okhttp、Glide、Zxing、dagger等等)
我们Android程序员每天都要和源码打交道.经过数年的学习,大多数程序员可以"写"代码,或者至少是拷贝并修改代码.而且,我们教授编程的方式强调编写代码的艺术,而不是如何阅读代码. ...
- Glide 4.9源码解析-缓存策略
本文Glide源码基于4.9,版本下载地址如下:Glide 4.9 前言 在分析了Glide的图片加载流程后,更加发觉到Glide的强大,于是这篇文章将继续深入分析Glide的缓存策略.不过今天的文章 ...
- BAT高级架构师合力熬夜15天,肝出了这份PDF版《Android百大框架源码解析》,还不快快码住。。。
前言 为什么要阅读源码? 现在中高级Android岗位面试中,对于各种框架的源码都会刨根问底,从而来判断应试者的业务能力边际所在.但是很多开发者习惯直接搬运,对各种框架的源码都没有过深入研究,在面试时 ...
- OkHttp 源码解析(4.9.1 版本)
文章目录 1.OkHttp 简介 2.OkHttp 配置与基本用法 2.1 依赖引入与配置 2.2 基本用法 3.OkHttp 常见对象介绍 4.OkHttp 源码解析 4.1 当我们调用`okhtt ...
- Android 图片加载框架Gilde源码解析
1.使用Gilde显示一张图片 Glide.with(this).load("https://cn.bing.com/sa/simg/hpb/xxx.jpg").into(imag ...
- Glide的源码解析(一)(附方法调用图)
前言 上一篇博客讲了Glide的基本使用,知其然,也要知其所以然,这篇博客一起来学习Glide的源码.如果不知道Glide的基本使用,可以看上一篇博客:http://blog.csdn.net/luo ...
最新文章
- html5中text-align,text-align
- 环保—北京周边 自行车骑行线路大全
- linux添加自己的库,Linux学习笔记——例叙makefile 增加自定义共享库
- IJCAI 2018:中科院计算所:增强对话生成一致性的序列到序列模型
- golang int64转string_(一)Golang从入门到原地起飞
- Java 数组插入元素
- thymeleaf的属性优先级
- 【白皮书分享】2020智能体白皮书-华为.pdf(附下载链接)
- SpringBoot加载静态资源
- SCI从入门到精通(二)——如何阅读文献
- servlet笔试题java,Java推荐!Servlet面试题和答案汇集
- Mac 快捷键 桌面壁纸
- Win10设置热点IP
- 流程图软件用哪款: Draw.io, 亿图图示, ProcessOn. 做最适合你的流程图
- 常见的图标库有哪些?
- 全志A33N切换分支.repo/repo/repo forall -c git checkout exdroid-7.1.1_r23-a33-v7.0rc2.1
- 微信刷票python代码_微信刷票漏洞详解, Python脚本实现一秒破万!
- Echarts示例大全 Demo合集网站
- [Windows] 迅雷 无修改 无限制 无视封锁
- 【资源分享(免积分)】增长黑客_创业公司的用户与收入增长秘籍 - 范冰(高清版免费).pdf
热门文章
- 自学了python基础英语_Python自学路线图之Python基础自学
- python列表取出元素_python中的列表,添加元素,获取元素,删除元素,列表分片,常用操作符...
- 能力素质有所欠缺_孩子说话啰嗦没重点?家长学会“大脑整理术”,提高孩子表达能力...
- nike附近门店查询_门店配送的全国服务网络如何快速成功运营?
- C++知识点60——非类型模板参数
- 从java到c_Binder机制,从Java到C (4. Parcel)
- mysql 错误1930xc1_Mysql写入记录出现 Incorrect string value: '\xB4\xE7\xB1\xCA\xBC\xC7‘错误?(写入中文)...
- 华为云AIOps实践全面解析
- Mac下编译Android源码,并导入IntelliJ IDEA进行源码阅读
- wince 6.0 串口 读取 readfile 超时问题