Android高手进阶之彻底了解DiskLruCache磁盘缓存机制原理

前言

DiskLruCache是一种管理数据存储的技术,单从Cache的字面意思也可以理解到,"Cache","高速缓存";

之前我们介绍过lrucache,没有看过老铁,可以从历史记录看;

今天我们来从源码上分析下DiskLruCache;

Android进阶之彻底理解LruCache缓存机制原理

一、为什么用DiskLruCache 1、LruCache和DiskLruCache

LruCache和DiskLruCache两者都是利用到LRU算法,通过LRU算法对缓存进行管理,以最近最少使用作为管理的依据,删除最近最少使用的数据,保留最近最常用的数据;

LruCache运用于内存缓存,而DiskLruCache是存储设备缓存;

2、为何使用DiskLruCache

离线数据存在的意义,当无网络或者是网络状况不好时,APP依然具备部分功能是一种很好的用户体验;

假设网易新闻这类新闻客户端,数据完全存储在缓存中而不使用DiskLruCache技术存储,那么当客户端被销毁,缓存被释放,意味着再次打开APP将是一片空白;

另外DiskLruCache技术也可为app“离线阅读”这一功能做技术支持;

DiskLruCache的存储路径是可以自定义的,不过也可以是默认的存储路径,而默认的存储路径一般是这样的:/sdcard/Android/data/包名/cache,包名是指APP的包名。我们可以在手机上打开,浏览这一路径;

二、DiskLruCache使用 1、添加依赖
// add dependence implementation 'com.jakewharton:disklrucache:2.0.2' 
2、创建DiskLruCache对象
/*  * directory – 缓存目录  * appVersion - 缓存版本  * valueCount – 每个key对应value的个数  * maxSize – 缓存大小的上限  */ DiskLruCache diskLruCache = DiskLruCache.open(directory, 1, 1, 1024 * 1024 * 10); 
3、添加 / 获取 缓存(一对一)
/**  * 添加一条缓存,一个key对应一个value  */ public void addDiskCache(String key, String value) throws IOException {     File cacheDir = context.getCacheDir();     DiskLruCache diskLruCache = DiskLruCache.open(cacheDir, 1, 1, 1024 * 1024 * 10);     DiskLruCache.Editor editor = diskLruCache.edit(key);     // index与valueCount对应,分别为0,1,2...valueCount-1     editor.newOutputStream(0).write(value.getBytes());      editor.commit();     diskLruCache.close(); } /**  * 获取一条缓存,一个key对应一个value  */ public void getDiskCache(String key) throws IOException {     File directory = context.getCacheDir();     DiskLruCache diskLruCache = DiskLruCache.open(directory, 1,
性激素检查 1, 1024 * 1024 * 10);     String value = diskLruCache.get(key).getString(0);     diskLruCache.close(); } 
4、添加 / 获取 缓存(一对多)
/**  * 添加一条缓存,1个key对应2个value  */ public void addDiskCache(String key, String value1, String value2) throws IOException {     File directory = context.getCacheDir();     DiskLruCache diskLruCache = DiskLruCache.open(directory, 1, 2, 1024 * 1024 * 10);     DiskLruCache.Editor editor = diskLruCache.edit(key);     editor.newOutputStream(0).write(value1.getBytes());     editor.newOutputStream(1).write(value2.getBytes());     editor.commit();     diskLruCache.close(); } /**  * 添加一条缓存,1个key对应2个value  */ public void getDiskCache(String key) throws IOException {     File directory = context.getCacheDir();     DiskLruCache diskLruCache = DiskLruCache.open(directory, 1, 2, 1024);     DiskLruCache.Snapshot snapshot = diskLruCache.get(key);     String value1 = snapshot.getString(0);     String value2 = snapshot.getString(1);     diskLruCache.close(); } 
三、源码分析

1、open()

DiskLruCache的构造方法是private修饰,这也就是告诉我们,不能通过new DiskLruCache来获取实例,构造方法如下:

private DiskLruCache(File directory, int appVersion,产品中心 int valueCount, long maxSize) {     this.directory = directory;     this.appVersion = appVersion;     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;     this.maxSize = maxSize; } 

但是提供了open()方法,供我们获取DiskLruCache的实例,open方法如下:

/**    * Opens the cache in {@code directory}, creating a cache if none exists    * there.    *    * @param directory a writable directory    * @param valueCount the number of values per cache entry. Must be positive.    * @param maxSize the maximum number of bytes this cache should use to store    * @throws IOException if reading or writing the cache directory fails    */   public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)       throws IOException {     if (maxSize <= 0) {       throw new IllegalArgumentException("maxSize <= 0");     }     if (valueCount <= 0) {       throw new IllegalArgumentException("valueCount <= 0");     }     // If a bkp file exists, use it instead.     //看备份文件是否存在     File backupFile = new File(directory, JOURNAL_FILE_BACKUP);    //如果备份文件存在,并且日志文件也存在,就把备份文件删除     //如果备份文件存在,日志文件不存在,就把备份文件重命名为日志文件      if (backupFile.exists()) {       File journalFile = new File(directory, JOURNAL_FILE);       // If journal file also exists just delete backup file.         //       if (journalFile.exists()) {         backupFile.delete();       } else {         renameTo(backupFile, journalFile, false);       }     }     // Prefer to pick up where we left off.     //初始化DiskLruCache,包括,大小,版本,路径,key对应多少value     DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);     //如果日志文件存在,就开始赌文件信息,并返回     //主要就是构建entry列表     if (cache.journalFile.exists()) {       try {         cache.readJournal();         cache.processJournal();         return cache;       } catch (IOException journalIsCorrupt) {         System.out             .println("DiskLruCache "                 + directory                 + " is corrupt: "                 + journalIsCorrupt.getMessage()                 + ", removing");         cache.delete();       }     }     //不存在就新建一个     // Create a new empty cache.     directory.mkdirs();     cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);     cache.rebuildJournal();     return cache;   } 

open函数:如果日志文件存在,直接去构建entry列表;如果不存在,就构建日志文件;

2、rebuildJournal()
构建文件:   //这个就是我们可以直接在disk里面看到的journal文件 主要就是对他的操作  private final File journalFile;  //journal文件的temp 缓存文件,一般都是先构建这个缓存文件,等待构建完成以后将这个缓存文件重新命名为journal  private final File journalFileTmp; /**    * Creates a new journal that omits redundant information. This replaces the    * current journal if it exists.    */   private synchronized void rebuildJournal() throws IOException {     if (journalWriter != null) {       journalWriter.close();     }     //指向journalFileTmp这个日志文件的缓存     Writer writer = new BufferedWriter(         new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));     try {       writer.write(MAGIC);       writer.write("\n");       writer.write(VERSION_1);       writer.write("\n");       writer.write(Integer.toString(appVersion));       writer.write("\n");       writer.write(Integer.toString(valueCount));       writer.write("\n");       writer.write("\n");       for (Entry entry : lruEntries.values()) {         if (entry.currentEditor != null) {           writer.write(DIRTY + ' ' + entry.key + '\n');         } else {           writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');         }       }     } finally {       writer.close();     }     if (journalFile.exists()) {       renameTo(journalFile, journalFileBackup, true);     }      //所以这个地方 构建日志文件的流程主要就是先构建出日志文件的缓存文件,如果缓存构建成功 那就直接重命名这个缓存文件,这样做好处在哪里?     renameTo(journalFileTmp, journalFile, false);     journalFileBackup.delete();     //这里也是把写入日志文件的writer初始化     journalWriter = new BufferedWriter(         new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));   } 

再来看当日志文件存在的时候,做了什么

3、readJournal()
private void readJournal() throws IOException { StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII); try { //读日志文件的头信息   String magic = reader.readLine();   String version = reader.readLine();   String appVersionString = reader.readLine();   String valueCountString = reader.readLine();   String blank = reader.readLine();   if (!MAGIC.equals(magic)