OkHttp源码分析(四)DiskLruCache

来源:互联网 发布:微信mac版 dmg 编辑:程序博客网 时间:2024/06/05 22:36

前一章讲到OKHTTP使用的缓存是DiskLruCache,DispLruCache是以LinkedHashMap为底层实现的磁盘缓存,但是具体是如何缓存的我还是不是很理解,作为程序员,不理解和咸鱼有什么分别。为了不做咸鱼,我们还是看一下源码吧!

public final class DiskLruCache implements Closeable, Flushable {  final FileSystem fileSystem;  final File directory;  private final File journalFile;  //DiskLruCache内部日志文件,对cache的每一次读写都对应一条日志记录,DiskLruCache通过分析日志分析和创建cache  private final File journalFileTmp;  private final File journalFileBackup;  private final int appVersion;  private long maxSize;  final int valueCount;  private long size = 0;  BufferedSink journalWriter;  final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);  // Must be read and written when synchronized on 'this'.  boolean initialized;  boolean closed;  boolean mostRecentTrimFailed;  boolean mostRecentRebuildFailed;  /**   * To differentiate between old and current snapshots, each entry is given a sequence number each   * time an edit is committed. A snapshot is stale if its sequence number is not equal to its   * entry's sequence number.   */  private long nextSequenceNumber = 0;  /** Used to run 'cleanupRunnable' for journal rebuilds. */  private final Executor executor;  private final Runnable cleanupRunnable = new Runnable() {    public void run() {        ......    }  };  ...  }

LinkedHashMap中的Entry对象,一个Entry对象对应一条cache记录。
一个Entry主要由以下几部分构成:
key:每个cache都有一个key作为其标识符。当前cache的key为其对应URL的MD5字符串
cleanFiles/dirtyFiles:每一个Entry对应多个文件,其对应的文件数由DiskLruCache.valueCount指定。当前在OkHttp中valueCount为2。即每个cache对应2个cleanFiles,2个dirtyFiles。其中第一个cleanFiles/dirtyFiles记录cache的meta数据(如URL,创建时间,SSL握手记录等等),第二个文件记录cache的真正内容。cleanFiles记录处于稳定状态的cache结果,dirtyFiles记录处于创建或更新状态的cache
currentEditor:entry编辑器,对entry的所有操作都是通过其编辑器完成。编辑器内部添加了同步锁。

同时Entry中还加入了清理线程,用来重建精简日志,当冗余日志超过日志文件本身的一般且总条数超过2000时执行

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;    Entry(String key) {      this.key = key;      lengths = new long[valueCount];      cleanFiles = new File[valueCount];      dirtyFiles = new File[valueCount];      // The names are repetitive so re-use the same builder to avoid allocations.      StringBuilder fileBuilder = new StringBuilder(key).append('.');      int truncateTo = fileBuilder.length();      for (int i = 0; i < valueCount; i++) {        fileBuilder.append(i);        cleanFiles[i] = new File(directory, fileBuilder.toString());        fileBuilder.append(".tmp");        dirtyFiles[i] = new File(directory, fileBuilder.toString());        fileBuilder.setLength(truncateTo);      }    }

SnapShot cache快照,记录了特定cache在某一个特定时刻的内容。每次向DiskLruCache请求时返回的都是目标cache的一个快照,相关逻辑在DiskLruCache.get中:

public synchronized Snapshot get(String key) throws IOException {    initialize();    checkNotClosed();    validateKey(key);    Entry entry = lruEntries.get(key);    if (entry == null || !entry.readable) return null;    Snapshot snapshot = entry.snapshot();    if (snapshot == null) return null;    redundantOpCount++;    //日志记录    journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n');    if (journalRebuildRequired()) {      executor.execute(cleanupRunnable);    }    return snapshot;  }

总结:
DiskLruCache主要有以下几个特点:

  1. 通过LinkedHashMap实现LRU替换
  2. 通过本地维护Cache操作日志保证Cache原子性与可用性,同时为防止日志过分膨胀定时执行日志精简
  3. 每一个Cache项对应两个状态副本:DIRTY,CLEAN。CLEAN表示当前可用状态Cache,外部访问到的cache快照均为CLEAN状态;DIRTY为更新态Cache。由于更新和创建都只操作DIRTY状态副本,实现了Cache的读写分离
  4. 每一个Cache项有四个文件,两个状态(DIRTY,CLEAN),每个状态对应两个文件:一个文件存储Cache meta数据,一个文件存储Cache内容数据
  5. DiskLruCache写cache时根据url的md5值找到Editor(如果没有则创建Editor),根据Editor就得到文件输出流,读cache时同样根据url的md5值找到snapshot对象,用snapshot对象得到文件输入流。
原创粉丝点击