Android高手进阶之彻底了解DiskLruCache磁盘缓存机制原理
吴振华 · 94浏览 · 发布于2021-09-14 +关注


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

前言

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) 
                      || !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 = 0; 
                  while (true) { 
                    try { 
                    //构建LruEntries entry列表 
                      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 { 
                    //初始化写入文件的writer 
                    journalWriter = new BufferedWriter(new OutputStreamWriter( 
                        new FileOutputStream(journalFile, true), Util.US_ASCII)); 
                  } 
                } finally { 
                  Util.closeQuietly(reader); 
                } 
                }

                  然后看下这个函数里面的几个主要变量:

                  //每个entry对应的缓存文件的格式 一般为1,也就是一个key,对应几个缓存,一般设为1,key-value一一对应的关系 
                  private final int valueCount; 
                  private long size = 0; 
                  //这个是专门用于写入日志文件的writer 
                  private Writer journalWriter; 
                  //这个集合应该不陌生了, 
                  private final LinkedHashMap<String, Entry> lruEntries = 
                          new LinkedHashMap<String, Entry>(0, 0.75f, true); 
                  //这个值大于一定数目时 就会触发对journal文件的清理了 
                  private int redundantOpCount;

                    下面就看下entry这个实体类的内部结构

                    private final class Entry { 
                            private final String key; 
                            /** 
                             * Lengths of this entry's files. 
                             * 这个entry中 每个文件的长度,这个数组的长度为valueCount 一般都是1 
                             */ 
                            private final long[] lengths; 
                            /** 
                             * True if this entry has ever been published. 
                             * 曾经被发布过 那他的值就是true 
                             */ 
                            private boolean readable; 
                            /** 
                             * The ongoing edit or null if this entry is not being edited. 
                             * 这个entry对应的editor 
                             */ 
                            private Editor currentEditor; 
                            @Override 
                            public String toString() { 
                                return "Entry{" + 
                                        "key='" + key + '\'' + 
                                        ", lengths=" + Arrays.toString(lengths) + 
                                        ", readable=" + readable + 
                                        ", currentEditor=" + currentEditor + 
                                        ", sequenceNumber=" + sequenceNumber + 
                                        '}'; 
                            } 
                            /** 
                             * The sequence number of the most recently committed edit to this entry. 
                             * 最近编辑他的序列号 
                             */ 
                            private long sequenceNumber; 
                            private Entry(String key) { 
                                this.key = key; 
                                this.lengths = new long[valueCount]; 
                            } 
                            public String getLengths() throws IOException { 
                                StringBuilder result = new StringBuilder(); 
                                for (long size : lengths) { 
                                    result.append(' ').append(size); 
                                } 
                                return result.toString(); 
                            } 
                            /** 
                             * Set lengths using decimal numbers like "10123". 
                             */ 
                            private void setLengths(String[] strings) throws IOException { 
                                if (strings.length != valueCount) { 
                                    throw invalidLengths(strings); 
                                } 
                                try { 
                                    for (int i = 0; i < strings.length; i++) { 
                                        lengths[i] = Long.parseLong(strings[i]); 
                                    } 
                                } catch (NumberFormatException e) { 
                                    throw invalidLengths(strings); 
                                } 
                            } 
                            private IOException invalidLengths(String[] strings) throws IOException { 
                                throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings)); 
                            } 
                            //臨時文件創建成功了以後 就會重命名為正式文件了 
                            public File getCleanFile(int i) { 
                                Log.v("getCleanFile","getCleanFile path=="+new File(directory, key + "." + i).getAbsolutePath()); 
                                return new File(directory, key + "." + i); 
                            } 
                            //tmp开头的都是临时文件 
                            public File getDirtyFile(int i) { 
                                Log.v("getDirtyFile","getDirtyFile path=="+new File(directory, key + "." + i + ".tmp").getAbsolutePath()); 
                                return new File(directory, key + "." + i + ".tmp"); 
                            } 
                    }

                      DiskLruCache的open函数的主要流程就基本走完了;

                      4、get()

                      /** 
                         * Returns a snapshot of the entry named {@code key}, or null if it doesn't 
                         * exist is not currently readable. If a value is returned, it is moved to 
                         * the head of the LRU queue. 
                         * 通过key获取对应的snapshot 
                         */ 
                        public synchronized Snapshot get(String key) throws IOException { 
                          checkNotClosed(); 
                          validateKey(key); 
                          Entry entry = lruEntries.get(key); 
                          if (entry == null) { 
                            return null; 
                          } 
                          if (!entry.readable) { 
                            return null; 
                          } 
                          // Open all streams eagerly to guarantee that we see a single published 
                          // snapshot. If we opened streams lazily then the streams could come 
                          // from different edits. 
                          InputStream[] ins = new InputStream[valueCount]; 
                          try { 
                            for (int i = 0; i < valueCount; i++) { 
                              ins[i] = new FileInputStream(entry.getCleanFile(i)); 
                            } 
                          } catch (FileNotFoundException e) { 
                            // A file must have been deleted manually! 
                            for (int i = 0; i < valueCount; i++) { 
                              if (ins[i] != null) { 
                                Util.closeQuietly(ins[i]); 
                              } else { 
                                break; 
                              } 
                            } 
                            return null; 
                          } 
                          redundantOpCount++; 
                          //在取得需要的文件以后 记得在日志文件里增加一条记录 并检查是否需要重新构建日志文件 
                          journalWriter.append(READ + ' ' + key + '\n'); 
                          if (journalRebuildRequired()) { 
                            executorService.submit(cleanupCallable); 
                          } 
                          return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths); 
                        }

                        再看一下,validateKey

                        5、validateKey

                        private void validateKey(String key) { 
                                Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); 
                                if (!matcher.matches()) { 
                                  throw new IllegalArgumentException("keys must match regex " 
                                          + STRING_KEY_PATTERN + ": \"" + key + "\""); 
                                } 
                          }

                          这里是对存储entry的map的key做了正则验证,所以key一定要用md5加密,因为有些特殊字符验证不能通过;

                          然后看这句代码对应的:

                          if (journalRebuildRequired()) { 
                                executorService.submit(cleanupCallable); 
                              }

                            对应的回调函数是:

                            /** This cache uses a single background thread to evict entries. */ 
                              final ThreadPoolExecutor executorService = 
                                  new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); 
                              private final Callable<Void> cleanupCallable = new Callable<Void>() { 
                                public Void call() throws Exception { 
                                  synchronized (DiskLruCache.this) { 
                                    if (journalWriter == null) { 
                                      return null; // Closed. 
                                    } 
                                    trimToSize(); 
                                    if (journalRebuildRequired()) { 
                                      rebuildJournal(); 
                                      redundantOpCount = 0; 
                                    } 
                                  } 
                                  return null; 
                                } 
                              };

                              其中的trimTOSize():

                              6、trimTOSize()

                              private void trimToSize() throws IOException { 
                                  while (size > maxSize) { 
                                    Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next(); 
                                    remove(toEvict.getKey()); 
                                  } 
                                }

                                就是检测总缓存是否超过了限制数量,

                                再来看journalRebuildRequired函数

                                7、journalRebuildRequired()

                                /** 
                                   * We only rebuild the journal when it will halve the size of the journal 
                                   * and eliminate at least 2000 ops. 
                                   */ 
                                  private boolean journalRebuildRequired() { 
                                    final int redundantOpCompactThreshold = 2000; 
                                    return redundantOpCount >= redundantOpCompactThreshold // 
                                        && redundantOpCount >= lruEntries.size(); 
                                  }

                                  就是校验redundantOpCount是否超出了范围,如果是,就重构日志文件;

                                  最后看get函数的返回值 new Snapshot()

                                  /** A snapshot of the values for an entry. */ 
                                  //这个类持有该entry中每个文件的inputStream 通过这个inputStream 可以读取他的内容 
                                    public final class Snapshot implements Closeable { 
                                      private final String key; 
                                      private final long sequenceNumber; 
                                      private final InputStream[] ins; 
                                      private final long[] lengths; 
                                      private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) { 
                                        this.key = key; 
                                        this.sequenceNumber = sequenceNumber; 
                                        this.ins = ins; 
                                        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); 
                                      } 
                                      /** Returns the unbuffered stream with the value for {@code index}. */ 
                                      public InputStream getInputStream(int index) { 
                                        return ins[index]; 
                                      } 
                                      /** Returns the string value for {@code index}. */ 
                                      public String getString(int index) throws IOException { 
                                        return inputStreamToString(getInputStream(index)); 
                                      } 
                                      /** Returns the byte length of the value for {@code index}. */ 
                                      public long getLength(int index) { 
                                        return lengths[index]; 
                                      } 
                                      public void close() { 
                                        for (InputStream in : ins) { 
                                          Util.closeQuietly(in); 
                                        } 
                                      } 
                                    }

                                    到这里就明白了get最终返回的其实就是entry根据key 来取的snapshot对象,这个对象直接把inputStream暴露给外面;

                                    8、save的过程

                                    public Editor edit(String key) throws IOException { 
                                        return edit(key, ANY_SEQUENCE_NUMBER); 
                                    } 
                                    //根据传进去的key 创建一个entry 并且将这个key加入到entry的那个map里 然后创建一个对应的editor 
                                    //同时在日志文件里加入一条对该key的dirty记录 
                                    private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { 
                                        //因为这里涉及到写文件 所以要先校验一下写日志文件的writer 是否被正确的初始化 
                                        checkNotClosed(); 
                                        //这个地方是校验 我们的key的,通常来说 假设我们要用这个缓存来存一张图片的话,我们的key 通常是用这个图片的 
                                        //网络地址 进行md5加密,而对这个key的格式在这里是有要求的 所以这一步就是验证key是否符合规范 
                                        validateKey(key); 
                                        Entry entry = lruEntries.get(key); 
                                        if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null 
                                                || entry.sequenceNumber != expectedSequenceNumber)) { 
                                            return null; // Snapshot is stale. 
                                        } 
                                        if (entry == null) { 
                                            entry = new Entry(key); 
                                            lruEntries.put(key, entry); 
                                        } else if (entry.currentEditor != null) { 
                                            return null; // Another edit is in progress. 
                                        } 
                                        Editor editor = new Editor(entry); 
                                        entry.currentEditor = editor; 
                                        // Flush the journal before creating files to prevent file leaks. 
                                        journalWriter.write(DIRTY + ' ' + key + '\n'); 
                                        journalWriter.flush(); 
                                        return editor; 
                                    }

                                      然后取得输出流

                                      public OutputStream newOutputStream(int index) throws IOException { 
                                              if (index < 0 || index >= valueCount) { 
                                                  throw new IllegalArgumentException("Expected index " + index + " to " 
                                                          + "be greater than 0 and less than the maximum value count " 
                                                          + "of " + valueCount); 
                                              } 
                                              synchronized (DiskLruCache.this) { 
                                                  if (entry.currentEditor != this) { 
                                                      throw new IllegalStateException(); 
                                                  } 
                                                  if (!entry.readable) { 
                                                      written[index] = true; 
                                                  } 
                                                  File dirtyFile = entry.getDirtyFile(index); 
                                                  FileOutputStream outputStream; 
                                                  try { 
                                                      outputStream = new FileOutputStream(dirtyFile); 
                                                  } catch (FileNotFoundException e) { 
                                                      // Attempt to recreate the cache directory. 
                                                      directory.mkdirs(); 
                                                      try { 
                                                          outputStream = new FileOutputStream(dirtyFile); 
                                                      } catch (FileNotFoundException e2) { 
                                                          // We are unable to recover. Silently eat the writes. 
                                                          return NULL_OUTPUT_STREAM; 
                                                      } 
                                                  } 
                                                  return new FaultHidingOutputStream(outputStream); 
                                              } 
                                          }

                                        注意这个index 其实一般传0 就可以了,DiskLruCache 认为 一个key 下面可以对应多个文件,这些文件 用一个数组来存储,所以正常情况下,我们都是

                                        一个key 对应一个缓存文件 所以传0

                                        //tmp开头的都是临时文件 
                                             public File getDirtyFile(int i) { 
                                                 return new File(directory, key + "." + i + ".tmp"); 
                                             }

                                          然后你这边就能看到,这个输出流,实际上是tmp 也就是缓存文件的 .tmp 也就是缓存文件的 缓存文件 输出流;

                                          这个流 我们写完毕以后 就要commit;

                                          public void commit() throws IOException { 
                                                  if (hasErrors) { 
                                                      completeEdit(this, false); 
                                                      remove(entry.key); // The previous entry is stale. 
                                                  } else { 
                                                      completeEdit(this, true); 
                                                  } 
                                                  committed = true; 
                                              }

                                            这个就是根据缓存文件的大小 更新disklrucache的总大小 然后再日志文件里对该key加入clean的log

                                            //最后判断是否超过最大的maxSize 以便对缓存进行清理 
                                            private synchronized void completeEdit(Editor editor, boolean success) throws IOException { 
                                                Entry entry = editor.entry; 
                                                if (entry.currentEditor != editor) { 
                                                    throw new IllegalStateException(); 
                                                } 
                                                // If this edit is creating the entry for the first time, every index must have a value. 
                                                if (success && !entry.readable) { 
                                                    for (int i = 0; i < valueCount; i++) { 
                                                        if (!editor.written[i]) { 
                                                            editor.abort(); 
                                                            throw new IllegalStateException("Newly created entry didn't create value for index " + i); 
                                                        } 
                                                        if (!entry.getDirtyFile(i).exists()) { 
                                                            editor.abort(); 
                                                            return; 
                                                        } 
                                                    } 
                                                } 
                                                for (int i = 0; i < valueCount; i++) { 
                                                    File dirty = entry.getDirtyFile(i); 
                                                    if (success) { 
                                                        if (dirty.exists()) { 
                                                            File clean = entry.getCleanFile(i); 
                                                            dirty.renameTo(clean); 
                                                            long oldLength = entry.lengths[i]; 
                                                            long newLength = clean.length(); 
                                                            entry.lengths[i] = newLength; 
                                                            size = size - oldLength + newLength; 
                                                        } 
                                                    } else { 
                                                        deleteIfExists(dirty); 
                                                    } 
                                                } 
                                                redundantOpCount++; 
                                                entry.currentEditor = null; 
                                                if (entry.readable | success) { 
                                                    entry.readable = true; 
                                                    journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); 
                                                    if (success) { 
                                                        entry.sequenceNumber = nextSequenceNumber++; 
                                                    } 
                                                } else { 
                                                    lruEntries.remove(entry.key); 
                                                    journalWriter.write(REMOVE + ' ' + entry.key + '\n'); 
                                                } 
                                                journalWriter.flush(); 
                                                if (size > maxSize || journalRebuildRequired()) { 
                                                    executorService.submit(cleanupCallable); 
                                                } 
                                            }

                                              commit以后 就会把tmp文件转正 ,重命名为 真正的缓存文件了;

                                              这个里面的流程和日志文件的rebuild 是差不多的,都是为了防止写文件的出问题。所以做了这样的冗余处理;

                                              总结

                                              DiskLruCache,利用一个journal文件,保证了保证了cache实体的可用性(只有CLEAN的可用),且获取文件的长度的时候可以通过在该文件的记录中读取。

                                              利用FaultHidingOutputStream对FileOutPutStream很好的对写入文件过程中是否发生错误进行捕获,而不是让用户手动去调用出错后的处理方法;


                                              相关推荐

                                              android下vulkan与opengles纹理互通

                                              talkchan · 476浏览 · 2020-11-23 10:37:39
                                              Android 使用RecyclerView实现轮播图

                                              奔跑的男人 · 1442浏览 · 2019-05-09 17:11:13
                                              微软发布新命令行工具 Windows Terminal

                                              吴振华 · 504浏览 · 2019-05-09 17:15:04
                                              Facebook 停止屏蔽部分区块链广告

                                              · 410浏览 · 2019-05-09 17:20:08
                                              加载中

                                              0评论

                                              评论
                                              坐标是江苏.南京,行业是互联网,技术是PHP和java,还有熟悉前后端等。
                                              分类专栏
                                              小鸟云服务器
                                              扫码进入手机网页