Android DiskLruCache 源码解析 硬盘缓存的绝佳方案

本文深入解析Android的DiskLruCache硬盘缓存框架,详细介绍了journal文件、DiskLruCache的open方法、缓存的存取过程以及其他关键方法。DiskLruCache通过journal文件保证了缓存的可用性和稳定性,同时提供了灵活的文件流操作,适用于存储小数据量。文章还探讨了可能的API扩展,以简化使用。
摘要由CSDN通过智能技术生成

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

                       
 

转载请标明出处:
  http://blog.csdn.net/lmj623565791/article/details/47251585
  本文出自:【张鸿洋的博客】

一、概述

依旧是整理东西,所以近期的博客涉及的东西可能会比较老一点,会分析一些经典的框架,我觉得可能也是每个优秀的开发者必须掌握的东西;那么对于Disk Cache,DiskLruCache可以算佼佼者了,所以我们就来分析下其源码实现。

对于该库的使用,推荐老郭的blog Android DiskLruCache完全解析,硬盘缓存的最佳方案

如果你不是很了解用法,那么注意下面的几点描述,不然直接看源码分析可能雨里雾里的。

  • 首先,这个框架会涉及到一个文件,叫做journal,这个文件中会存储每次读取操作的记录;
  • 对于获取一个DiskLruCache,是这样的:

    DiskLruCache.open(directory, appVersion,                     valueCount, maxSize) ;
        
        
        
    • 1
    • 2
  • 关于存一般是这么使用的:

    String key = generateKey(url);  DiskLruCache.Editor editor = mDiskLruCache.edit(key); OuputStream os = editor.newOutputStream(0); 
        
        
        
    • 1
    • 2
    • 3

    因为每个实体都是个文件,所以你可以认为这个os指向一个文件的FileOutputStream,然后把你想存的东西写入就行了,写完以后记得调用:editor.commit()

  • 关于取一般是这样的:

     DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);  if (snapShot != null) {      InputStream is = snapShot.getInputStream(0);  }
        
        
        
    • 1
    • 2
    • 3
    • 4

    还是那句,因为每个实体都是文件,所以你返回的is是个FileInputStream,你可以利用is读取出里面的内容,然后do what you want .

好了,关于Cache最主要就是存取了,了解这几点,就可以往下去看源码分析了。

还记得第一点说的journal文件么,首先就是它了。


二、journal文件

journal文件你打开以后呢,是这个格式;

libcore.io.DiskLruCache111DIRTY c3bac86f2e7a291a1a200b853835b664CLEAN c3bac86f2e7a291a1a200b853835b664 4698READ c3bac86f2e7a291a1a200b853835b664DIRTY c59f9eec4b616dc6682c7fa8bd1e061fCLEAN c59f9eec4b616dc6682c7fa8bd1e061f 4698READ c59f9eec4b616dc6682c7fa8bd1e061fDIRTY be8bdac81c12a08e15988555d85dfd2bCLEAN be8bdac81c12a08e15988555d85dfd2b 99READ be8bdac81c12a08e15988555d85dfd2bDIRTY 536788f4dbdffeecfbb8f350a941eea3REMOVE 536788f4dbdffeecfbb8f350a941eea3 
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

首先看前五行:

  • 第一行固定字符串libcore.io.DiskLruCache
  • 第二行DiskLruCache的版本号,源码中为常量1
  • 第三行为你的app的版本号,当然这个是你自己传入指定的
  • 第四行指每个key对应几个文件,一般为1
  • 第五行,空行

ok,以上5行可以称为该文件的文件头,DiskLruCache初始化的时候,如果该文件存在需要校验该文件头。

接下来的行,可以认为是操作记录。

  • DIRTY 表示一个entry正在被写入(其实就是把文件的OutputStream交给你了)。那么写入分两种情况,如果成功会紧接着写入一行CLEAN的记录;如果失败,会增加一行REMOVE记录。
  • REMOVE除了上述的情况呢,当你自己手动调用remove(key)方法的时候也会写入一条REMOVE记录。
  • READ就是说明有一次读取的记录。
  • 每个CLEAN的后面还记录了文件的长度,注意可能会一个key对应多个文件,那么就会有多个数字(参照文件头第四行)。

从这里看出,只有CLEAN且没有REMOVE的记录,才是真正可用的Cache Entry记录。

分析完journal文件,首先看看DiskLruCache的创建的代码。


三、DiskLruCache#open

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)      throws IOException {    // 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 cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);    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;  }
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

首先检查存不存在journal.bkp(journal的备份文件)

如果存在:然后检查journal文件是否存在,如果正主在,bkp文件就可以删除了。
如果不存在,将bkp文件重命名为journal文件。

接下里判断journal文件是否存在:

  • 如果不存在

    创建directory;重新构造disklrucache;调用rebuildJournal建立journal文件

    /*** 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();}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();journalWriter = new BufferedWriter(    new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));}
        
        
        
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    可以看到首先构建一个journal.tmp文件,然后写入文件头(5行),然后遍历lruEntries(lruEntries =
      new LinkedHashMap<String, Entry>(0, 0.75f, true);
    ),当然我们这里没有任何数据。接下来将tmp文件重命名为journal文件。

  • 如果存在

    如果已经存在,那么调用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)      || !VERSION_1.equals(version)      || !Integer.toString(appVersion).equals(appVersionString)      || !Integer.toString(valueCount).equals(valueCountString)      || !"".equals(blank)) {    throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "        + valueCountString + ", " + blank + "]");  }  int lineCount = 0while (true) {    try {      readJournalLine(reader.readLine());      lineCount++;    } catch (EOFException endOfJournal) {      break;    }  }  redundantOpCount = lineCount - lruEntries.size();  // If we ended on a truncated line, rebuild the journal before appending to it.  if (reader.hasUnterminatedLine()) {    rebuildJournal();  } else {    journalWriter = new BufferedWriter(new OutputStreamWriter(        new FileOutputStream(journalFile, true), Util.US_ASCII));  }} finally {  Util.closeQuietly(reader);}}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值