本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
Android高手进阶之彻底了解DiskLruCache磁盘缓存机制原理
吴振华 · 413浏览 · 发布于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(s                
                                    
                                            
                    					
                                    
                                    

                    相关推荐

                    android下vulkan与opengles纹理互通

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

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

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

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

                    0评论

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