Volley的cache之硬盘缓存--DiskBasedCache

2024-09-01 11:32

本文主要是介绍Volley的cache之硬盘缓存--DiskBasedCache,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

衡量一个框架的优劣,缓存的处理是很重要的指标。这次我将对Volley的硬盘缓存DiskBasedCache从源码的角度进行解析。
下面先对DiskBasedCache的原理做简要介绍,开个头,然后根据简介做源码分析。
缓存原理

在说缓存原理之前,要说一下缓存的数据怎么来的。
第一步:
当NetWorkDispatcher的run方法开始执行(NetWorkDispatcher是Thread类的子类,后面会写文章介绍),进入循环,从网络请求队列中取出一个请求对象,执行网络请求。
第二步:
将从服务器得到的数据转换为Response对象(此对象代表一个网络响应)。
第三步:
根据 请求对象是否要求缓存(在新建Request的时候设置的值)来决定是否将响应数据写入缓存中。
以上三步得到缓存数据。缓存包括: 网络响应的响应正文和头信息。
得到缓存数据以后就会调用Cache类或者其子类的put方法将缓存信息写入SD卡(这里可以看出,Volley并没有做内存的缓存而是直接写入到磁盘文件)。调用get方法从SD卡取出缓存数据。
知道一点音频知识的都知道,在音频文件中会有该音频的头部信息,用来描述该音频的一些属性。在Volley中,为了描述缓存文件,google攻城狮也将缓存文件的一些重要属性—缓存文件的大小,缓存对应的URL,服务器的响应时间,网络延迟和缓存的新鲜度作为头信息组成CacheHeader对象写入到缓存文件中,并将响应正文写入CacheHeader后面,组成一个缓存文件。
在取缓存的时候,根据内存中的CacheHeader对象(缓存文件属性的封装类)的map集合(此map的key为待请求的URL,Value是CacheHeader对象)判断此请求对象是否有缓存。如果有缓存的话,将缓存读出来,封装成Entry(缓存属性和数据的封装类),从而恢复成response对象。
以上一写一读就完成了缓存的写入和取出操作。
源代码解析

介绍完了以上的原理,接下来就是代码实现了。我以方法作为单位,对DiskBasedCache源码进行解析。
put(string,entry)写缓存

此方法主要用来将缓存写入到SD卡,并在内存中加入该缓存文件的头信息(CacheHeader)、代码如下:

/*** Puts the entry with the specified key into the cache.*/@Overridepublic synchronized void put(String key, Entry entry) {pruneIfNeeded(entry.data.length);File file = getFileForKey(key);try {FileOutputStream fos = new FileOutputStream(file);CacheHeader e = new CacheHeader(key, entry);e.writeHeader(fos);fos.write(entry.data);fos.close();putEntry(key, e);return;} catch (IOException e) {}boolean deleted = file.delete();if (!deleted) {VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());}}

方法体的第一行代码的作用是进行 缓存的管理。这一点下面会介绍,先跳过。
接下来,根据请求Url新建缓存文件。然后使用字节流将数据写入该文件中。注意代码将写头部信息和写数据是分开的,这里是因为头部信息是有一定的结构,必须按照头信息的格式写入文件。写完之后,将代表缓存信息的CacheHeader放入内存方便以后对缓存的检索。如果在写文件中遇到异常,删除缓存文件。如果删除不成功,打Log。其实代码很容易理解。其中一些小点,比如 头信息怎么写的?会在接下来说明。
get(string)读缓存

写的逻辑大家都已经清晰了,读逻辑是写逻辑的逆过程。先上代码:

/*** Returns the cache entry with the specified key if it exists, null otherwise.*/@Overridepublic synchronized Entry get(String key) {CacheHeader entry = mEntries.get(key);// if the entry does not exist, return.if (entry == null) {return null;}File file = getFileForKey(key);CountingInputStream cis = null;try {cis = new CountingInputStream(new FileInputStream(file));CacheHeader.readHeader(cis); // eat headerbyte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));return entry.toCacheEntry(data);} catch (IOException e) {VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());remove(key);return null;} finally {if (cis != null) {try {cis.close();} catch (IOException ioe) {return null;}}}}

首先根据内存中的头信息,判断是否有该key的缓存。有的话,进入if后面的语句:
首先按照put方法中得到文件的做法,来得到缓存文件。接着用一个字节输入流的包装类CountingInputStream读取缓存文件,此类有一个功能–记住已经读取的字节数,从而方便的读取头信息这类有结构的数据(很赞的想法)。将读取到的头信息和读取到的数据组成entry对象返回。与put过程一样,这里也会涉及到头的处理,包括缓存的校验。接下来会说。
CacheHeader缓存文件头信息类

该类封装了 缓存文件的大小,缓存对应的Url,服务器响应的日期,网络延迟,缓存文件的新鲜度,响应的头信息。
该类包含了三个重要方法:
readHeader(InputStream is)读头信息

代码如下:

public static CacheHeader readHeader(InputStream is) throws IOException {CacheHeader entry = new CacheHeader();int magic = readInt(is);if (magic != CACHE_MAGIC) {// don't bother deleting, it'll get pruned eventuallythrow new IOException();}entry.key = readString(is);entry.etag = readString(is);if (entry.etag.equals("")) {entry.etag = null;}entry.serverDate = readLong(is);entry.ttl = readLong(is);entry.softTtl = readLong(is);entry.responseHeaders = readStringStringMap(is);return entry;}

在方法体的第三行,验证输入流对应的缓存文件的合法性。如果读出的magic不等于写入的magic,终止读写,如果合法,从缓存文件中读出结构数据,组成对象返回。
writeHeader(OutputStream os)

与上面方法相反,这里将maic和一些属性信息写入到缓存文件。注意写文件也是按照结构来写。

  public boolean writeHeader(OutputStream os) {try {writeInt(os, CACHE_MAGIC);writeString(os, key);writeString(os, etag == null ? "" : etag);writeLong(os, serverDate);writeLong(os, ttl);writeLong(os, softTtl);writeStringStringMap(responseHeaders, os);os.flush();return true;} catch (IOException e) {VolleyLog.d("%s", e.toString());return false;}}

toCacheEntry(byte[] data)方法

此方法用于将头信息和数据信息封装成Entry(Cache的单位数据)返回,代码很简单。

public Entry toCacheEntry(byte[] data) {Entry e = new Entry();e.data = data;e.etag = etag;e.serverDate = serverDate;e.ttl = ttl;e.softTtl = softTtl;e.responseHeaders = responseHeaders;return e;}

以上就是关于CacheHeader的介绍。

了解完以上内容,基本上对DiskBasedCache掌握了60%-70%。还有一些很好的方法,接下来继续解析。
pruneIfNeeded(int neededSpace)压缩缓存

说不上压缩,只是不知道如何翻译 prune.这个方法判断 待写入文件 写入之后是否会超出 缓存的最大值(默认5M)。如果超出,就删除掉之前的缓存数据。代码也好懂,如下:

private void pruneIfNeeded(int neededSpace) {if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {return;}if (VolleyLog.DEBUG) {VolleyLog.v("Pruning old cache entries.");}long before = mTotalSize;int prunedFiles = 0;long startTime = SystemClock.elapsedRealtime();Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, CacheHeader> entry = iterator.next();CacheHeader e = entry.getValue();boolean deleted = getFileForKey(e.key).delete();if (deleted) {mTotalSize -= e.size;} else {VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",e.key, getFilenameForKey(e.key));}iterator.remove();prunedFiles++;if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {break;}}if (VolleyLog.DEBUG) {VolleyLog.v("pruned %d files, %d bytes, %d ms",prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);}}

看似很长,实则很优美。主要逻辑在 cacheHeader组成的map的遍历,然后找到头信息所指的缓存文件,删除之。
initialize() 缓存初始化

在缓存处理线程启动的时候,先加载本地已有的缓存文件的头信息到内存方便检索。就是这个作用。代码很清晰:

public synchronized void initialize() {if (!mRootDirectory.exists()) {if (!mRootDirectory.mkdirs()) {VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());}return;}File[] files = mRootDirectory.listFiles();if (files == null) {return;}for (File file : files) {FileInputStream fis = null;try {fis = new FileInputStream(file);CacheHeader entry = CacheHeader.readHeader(fis);entry.size = file.length();putEntry(entry.key, entry);} catch (IOException e) {if (file != null) {file.delete();}} finally {try {if (fis != null) {fis.close();}} catch (IOException ignored) { }}}}

总结

到此为止,DiskBasedCache大部分都介绍过了,还有一些方法,如:
readInt 和writeInt
readLong和 writeLong
readString和writeString
readStringStringMap和writeStringStringMap
这些方法主要用于头信息读写有结构的数据,其中夹杂着一些自定义的位运算。

就像方法的代码一样,单个代码很好懂,代码写的很优雅,简洁(写简洁易懂的代码也是一门功课)。既然代码不是难点,那么理清楚方法之间的逻辑关系,就成为要下功夫的地方。读源代码不一定要一遍看懂,首先根据方法名知道方法大概的用途,然后将各个方法的调用关系搞清楚这个类是怎么工作的,最后再一行一行的读代码。这样下来,至少得几遍。还有就是 方法的修饰符 如 public private protected和类的修饰符 static等也成为我们理解的垫脚石。

转载:http://blog.csdn.net/yuan514168845/article/details/49665043

这篇关于Volley的cache之硬盘缓存--DiskBasedCache的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1126808

相关文章

Redis与缓存解读

《Redis与缓存解读》文章介绍了Redis作为缓存层的优势和缺点,并分析了六种缓存更新策略,包括超时剔除、先删缓存再更新数据库、旁路缓存、先更新数据库再删缓存、先更新数据库再更新缓存、读写穿透和异步... 目录缓存缓存优缺点缓存更新策略超时剔除先删缓存再更新数据库旁路缓存(先更新数据库,再删缓存)先更新数

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧

SpringBoot使用注解集成Redis缓存的示例代码

《SpringBoot使用注解集成Redis缓存的示例代码》:本文主要介绍在SpringBoot中使用注解集成Redis缓存的步骤,包括添加依赖、创建相关配置类、需要缓存数据的类(Tes... 目录一、创建 Caching 配置类二、创建需要缓存数据的类三、测试方法Spring Boot 熟悉后,集成一个外

使用Spring Cache时设置缓存键的注意事项详解

《使用SpringCache时设置缓存键的注意事项详解》在现代的Web应用中,缓存是提高系统性能和响应速度的重要手段之一,Spring框架提供了强大的缓存支持,通过​​@Cacheable​​、​​... 目录引言1. 缓存键的基本概念2. 默认缓存键生成器3. 自定义缓存键3.1 使用​​@Cacheab

Nacos客户端本地缓存和故障转移方式

《Nacos客户端本地缓存和故障转移方式》Nacos客户端在从Server获得服务时,若出现故障,会通过ServiceInfoHolder和FailoverReactor进行故障转移,ServiceI... 目录1. ServiceInfoHolder本地缓存目录2. FailoverReactorinit

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

Redis中使用布隆过滤器解决缓存穿透问题

一、缓存穿透(失效)问题 缓存穿透是指查询一个一定不存在的数据,由于缓存中没有命中,会去数据库中查询,而数据库中也没有该数据,并且每次查询都不会命中缓存,从而每次请求都直接打到了数据库上,这会给数据库带来巨大压力。 二、布隆过滤器原理 布隆过滤器(Bloom Filter)是一种空间效率很高的随机数据结构,它利用多个不同的哈希函数将一个元素映射到一个位数组中的多个位置,并将这些位置的值置

防止缓存击穿、缓存穿透和缓存雪崩

使用Redis缓存防止缓存击穿、缓存穿透和缓存雪崩 在高并发系统中,缓存击穿、缓存穿透和缓存雪崩是三种常见的缓存问题。本文将介绍如何使用Redis、分布式锁和布隆过滤器有效解决这些问题,并且会通过Java代码详细说明实现的思路和原因。 1. 背景 缓存穿透:指的是大量请求缓存中不存在且数据库中也不存在的数据,导致大量请求直接打到数据库上,形成数据库压力。 缓存击穿:指的是某个热点数据在

PHP APC缓存函数使用教程

APC,全称是Alternative PHP Cache,官方翻译叫”可选PHP缓存”。它为我们提供了缓存和优化PHP的中间代码的框架。 APC的缓存分两部分:系统缓存和用户数据缓存。(Linux APC扩展安装) 系统缓存 它是指APC把PHP文件源码的编译结果缓存起来,然后在每次调用时先对比时间标记。如果未过期,则使用缓存的中间代码运行。默认缓存 3600s(一小时)。但是这样仍会浪费大量C

缓存策略使用总结

缓存是提高系统性能的最简单方法之一。相对而言,数据库(or NoSQL数据库)的速度比较慢,而速度却又是致胜的关键。 如果使用得当,缓存可以减少相应时间、减少数据库负载以及节省成本。本文罗列了几种缓存策略,选择正确的一种会有很大的不同。缓存策略取决于数据和数据访问模式。换句话说,数据是如何写和读的。例如: 系统是写多读少的吗?(例如基于时间的日志)数据是否是只写入一次并被读取多次?(例如用户配