DiskLRUCache

DiskLRUCache是Android中实现磁盘缓存相关的组件类,当缓存满时其使用最近最少使用策略来淘汰相关的元素,以控制缓存大小。本文主要基于DiskLRUCache相关源码分析DiskLRUCache的创建、缓存的添加、获取、删除流程。

DiskLRUCache创建

DiskLRUCache不允许直接创建,可以通过调用open方法去创建

	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");
    }
    File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
    if (backupFile.exists()) {
       //如果备份的目录文件存在,尝试获取目录文件,如果目录文件存在,旧删除备份目录文件,如果不存在就将备份目录文件重命名为目录文件。
      File journalFile = new File(directory, JOURNAL_FILE);
      if (journalFile.exists()) {
        backupFile.delete();
      } else {
        renameTo(backupFile, journalFile, false);
      }
    }

    // 创建DiskLruCache对象
    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
    if (cache.journalFile.exists()) {
      try {
         //读取并解析目录文件
        cache.readJournal();
        cache.processJournal();
        return cache;
      } catch (IOException journalIsCorrupt) {
        cache.delete();
      }
    }
  }

其创建时的参数说明如下:

  1. directory: 缓存目录,
  2. appVersion: app版本,当appVersion更新后, 会自动清除老数据,一般我们是不需要清除老数据的,所以一般这个不变。
  3. valueCount: 一个节点对应的文件数
  4. maxSize:缓存大小

open方法主要做了三件事:

  1. 确认目录文件
  2. 创建DiskLruCache对象
  3. 读取目录文件内容到内存

那目录文件到底是啥样的呢?如下

     *     libcore.io.DiskLruCache
     *     1
     *     1
     *     1
     *
     *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
     *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
     *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
     *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
     *     DIRTY 1ab96a171faeeee38496d8b330771a7a
     *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
     *     READ 335c4c6028171cfddfbaae1a9c313c52
     *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6

我们来分别对每一行进行分析吧:
1、想都不用想,就是告诉我们该缓存用的是DiskLruCache。
2、DiskLruCache的版本号。
3、应用程序的版本号。
4、在DiskLruCache.open()方法中传入,表示一个key可以对应几个缓存文件,一般我们都传1,表示一key一Value。
5、DIRTY开头,后面紧跟md5编码的key值,也就是缓存文件的名字,表示我们正在向缓存文件中写入一条数据,缓存文件的名字为后面的key值,可能写入成功,也可能写入失败,故标记为dirty。
6、CLEAN开头,后面紧跟缓存文件的名称,表示该数据写入成功了,再后面又有一组数字,其实该组数字是我们写入的数据字节大小,比如我们写入的是图片,那说明该图片的大小为17352个字节。
7、READ开头,后面紧跟缓存文件的名称,表示我们读取了该名称的缓存文件的数据。
在创建DiskLruCache前会将目录文件中每一行读入内存,实际上每一行的操作指令就是访问顺序,根据Dirty/Clean/Read/Remove操作符来调用对应Entry的插入删除等操作,以此来构建最近最少使用记录。

添加缓存

添加缓存主要通过Editor获取对应文件流,然后写入内容后调用commit,获取Ediitor时有2个操作

  1. 通过key获取一个Editor,此操作会先去lruEntries中获取Entry节点,没有的话会自动创建一个Entry节点,并给该节点绑定一个Editor对象。
  2. 向目录文件写入DIRTY操作指令
 	public Editor edit(String key) throws IOException {
   	 return edit(key, ANY_SEQUENCE_NUMBER);
  	}

  private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
    checkNotClosed();
    Entry entry = lruEntries.get(key);
    if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
        || entry.sequenceNumber != expectedSequenceNumber)) {
      return null; // Value 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.append(DIRTY);
    journalWriter.append(' ');
    journalWriter.append(key);
    journalWriter.append('\n');
    journalWriter.flush();
    return editor;
  }

获取到Editor对象后,可以通过getFile方法获取对应文件File对象,通过OutputStream写入到文件中,然后调用commit方法。

  public File getFile(int index) throws IOException {
      synchronized (DiskLruCache.this) {
        if (entry.currentEditor != this) {
            throw new IllegalStateException();
        }
        if (!entry.readable) {
            written[index] = true;
        }
        File dirtyFile = entry.getDirtyFile(index);
        if (!directory.exists()) {
            directory.mkdirs();
        }
        return dirtyFile;
      }
    }

获取缓存

调用get方法获取缓存,会返回一个Snapshot对象,该对象存储对应key和缓存文件的InputStream,并且向目录文件中写入Read标签的记录。

  public synchronized Snapshot get(String key) throws IOException {
    //通过key获取到对应的Entry
    Entry entry = lruEntries.get(key);
    if (entry == null) {
      return null;
    }
    InputStream[] ins = new InputStream[valueCount];
    try {
      for (int i = 0; i < valueCount; i++) {
        ins[i] = new FileInputStream(entry.getCleanFile(i));
      }
    } catch (FileNotFoundException e) {
      return null;
    }

    redundantOpCount++;
    journalWriter.append(READ + ' ' + key + '\n');
    if (journalRebuildRequired()) {
      executorService.submit(cleanupCallable);
    }

    return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
  }
	public final class Snapshot implements Closeable {
    private final String key;
    private final long sequenceNumber;
    private final InputStream[] ins;
    private final long[] lengths;
 }

删除缓存

删除缓存主要涉及三件事:

  1. 从map中删除该节点
  2. 向目录文件中心写入REMOVE标签
  3. 重新计算当前缓存的大小
public synchronized boolean remove(String key) throws IOException {
    checkNotClosed();
    validateKey(key);
    Entry entry = lruEntries.get(key);
    if (entry == null || entry.currentEditor != null) {
      return false;
    }

    for (int i = 0; i < valueCount; i++) {
      File file = entry.getCleanFile(i);
      if (file.exists() && !file.delete()) {
        throw new IOException("failed to delete " + file);
      }
      //调整缓存大小
      size -= entry.lengths[i];
      entry.lengths[i] = 0;
    }

    redundantOpCount++;
    journalWriter.append(REMOVE + ' ' + key + '\n');
    lruEntries.remove(key);

    if (journalRebuildRequired()) {
    //cleanupCallable这个执行的时候会调用trimToSize方法,执行超出最大容量后的最老节点的移除
      executorService.submit(cleanupCallable);
    }

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

  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LRU Cache (最近最少使用缓存) 和 DiskLruCache (基于磁盘的 LRU 缓存) 是两种常见的缓存技术,它们的使用场景和优点如下: 1. LRU Cache 的使用场景: - 当需要缓存一些数据时,但是又不能无限制地增加内存消耗时,可以使用 LRU Cache 进行缓存。 - 当需要快速访问某些数据时,而这些数据的访问频率比较高时,可以使用 LRU Cache 进行缓存。 - 当需要保证缓存数据的时效性,避免过期数据对程序造成影响时,可以使用 LRU Cache 进行缓存。 2. DiskLruCache 的使用场景: - 当需要缓存一些大量的数据时,但是这些数据又不能全部存放在内存中时,可以使用 DiskLruCache 进行缓存。 - 当需要保证数据能够持久化存储时,可以使用 DiskLruCache 进行缓存。 - 当需要对缓存数据进行一些额外的操作时,例如压缩、加密等操作时,可以使用 DiskLruCache 进行缓存。 以下是使用 Kotlin 代码展示 LRU Cache 和 DiskLruCache 的实现方法: ```kotlin // LRU Cache 的实现 import android.util.LruCache // 初始化一个 LRU Cache,设置最大缓存数量为 10 个 val lruCache = LruCache<String, String>(10) // 将数据加入缓存中 lruCache.put("key1", "value1") // 获取缓存中的数据 val value = lruCache.get("key1") // 移除缓存中的数据 lruCache.remove("key1") // 清除缓存中的所有数据 lruCache.evictAll() ``` ```kotlin // DiskLruCache 的实现 import com.jakewharton.disklrucache.DiskLruCache import java.io.File // 初始化一个 DiskLruCache,设置缓存目录和最大缓存数量为 10 个 val directory = File(context.cacheDir, "disk_cache") val diskCacheSize = 10 * 1024 * 1024 // 10MB val diskLruCache = DiskLruCache.open(directory, 1, 1, diskCacheSize.toLong()) // 将数据加入缓存中 val editor = diskLruCache.edit("key1") editor?.newOutputStream(0)?.use { outputStream -> outputStream.write("value1".toByteArray()) } editor?.commit() // 获取缓存中的数据 val snapshot = diskLruCache.get("key1") val value = snapshot?.getInputStream(0)?.bufferedReader().use { reader -> reader?.readText() } // 移除缓存中的数据 diskLruCache.remove("key1") // 清除缓存中的所有数据 diskLruCache.delete() ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值