关于Android的 DiskLruCache磁盘缓存机制原理

目录
  • 一、为什么用DiskLruCache
    • 1、LruCache和DiskLruCache
    • 2、为何使用DiskLruCache
  • 二、DiskLruCache使用
    • 1、添加依赖
    • 2、创建DiskLruCache对象
    • 3、添加 / 获取 缓存(一对一)
    • 4、添加 / 获取 缓存(一对多)
  • 三、源码分析
    • 1、open()
    • 2、rebuildJournal()
    • 3、readJournal()
    • 4、get()
    • 5、validateKey
    • 6、trimTOSize()
    • 7、journalRebuildRequired()
    • 8、save的过程

一、为什么用DiskLruCache

1、LruCache和DiskLruCache

LruCacheDiskLruCache两者都是利用到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");
        }
}

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

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);
  }

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 + "\"");
        }
  }

这里是对存储entrymap的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加入cleanlog

//最后判断是否超过最大的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的可用),且获取文件的长度的时候可以通过在该文件的记录中读取。

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

到此这篇关于关于Android DiskLruCache的磁盘缓存机制原理的文章就介绍到这了,更多相关Android DiskLruCache磁盘缓存机制原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android实现清除应用缓存功能

    本文实例为大家分享了Android清除应用缓存的具体代码,供大家参考,具体内容如下 import android.content.Context; import android.os.Environment; import java.io.File; import java.math.BigDecimal; /** * 获取缓存大小并清理缓存 */ public class DataCleanManagerUtils { /** * Context.getExternalFilesDir() -

  • Android缓存之DiskLruCache磁盘缓存的使用

    DiskLruCache和LruCache不同的是,LruCache是内存缓存,而DiskLruCache是指磁盘缓存,顾名思义就是把文件缓存到磁盘,也也就是手机的内存卡中.接下来先简单介绍DiskLruCache的使用方法. 下载源码 DiskLruCache并没有在 SDK中存在,但又是谷歌提倡的.所以我们要先把DiskLruCache的源码下载下来. 我们可以通过下面这个地址下载源码:https://github.com/JakeWharton/DiskLruCache/tree/mast

  • 浅谈Android LruCache的缓存策略

    一.Android中的缓存策略 一般来说,缓存策略主要包含缓存的添加.获取和删除这三类操作.如何添加和获取缓存这个比较好理解,那么为什么还要删除缓存呢?这是因为不管是内存缓存还是硬盘缓存,它们的缓存大小都是有限的.当缓存满了之后,再想其添加缓存,这个时候就需要删除一些旧的缓存并添加新的缓存. 因此LRU(Least Recently Used)缓存算法便应运而生,LRU是近期最少使用的算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象.采用LRU算法的缓存有两种:LrhCach

  • Android 获取应用缓存大小与清除缓存的方法

    如下所示: package com.lucasey.littleant.frame; /** * 文 件 名: FileCacheUtils.java * 描 述: 主要功能有清除内/外缓存,清除数据库,清除sharedPreference,清除files和清除自定义目录 * */ import java.io.File; import java.math.BigDecimal; import android.content.Context; import android.os.Environm

  • Android 边播边缓存的实现(MP4 未加密m3u8)

    实现思路 红色框的 ProxyServer就是需要实现的一个代理服务器. 当客户端拿到一个视频的url(mp4或者m3u8)时,通过proxyServer转化为一个代理的url,然后请求代理服务器:代理服务器接收到客户端的请求后,先查看本地是否存在缓存,如果不存在则向真实服务器发送请求,拿到结果后再存到本地. 实现重点 缓存是一个代理服务器的主要部分,所以这部分是一个重点.本设计的缓存是一个分片的LRU缓存.分片的好处是灵活方便做LRU.当真实服务器返回一个大文件时,我们在进行切割后缓存在本地,

  • Android Studio缓存文件夹配置教程

    安装完,或者绿色版解压完,先别打开Android Stduio.要先配置下Android Studio 的缓存路径. 这个缓存文件主要是存放一些AndroidStudio设置和插件和项目的缓存信息的. 我用的是AS的老版本,缓存文件夹如图.默认是放在C盘系统盘里面的,这里是没改直接打开的,实际上安装完应该先别打开修改完再打开! 打开会看到 为什么Android Stduio启动速度比Eclipse快,也是托这个缓存文件夹的关系.但缺点是第一次建立缓存会比较慢. 为什么要配置这个文件呢? 因为这个

  • Android缓存机制——LruCache的详解

    概述 LruCache的核心原理就是对LinkedHashMap的有效利用,它的内部存在一个LinkedHashMap成员变量,值得注意的4个方法:构造方法.get.put.trimToSize LRU(Least Recently Used)缓存算法便应运而生,LRU是最近最少使用的算法,它的核心思想是当缓存满时,会优先淘汰那些最近最少使用的缓存对象.采用LRU算法的缓存有两种:LrhCache和DisLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法. LRU原理

  • android实现清理缓存功能

    android之清理缓存实现,供大家参考,具体内容如下 一. 清理缓存首先要搞清楚清理哪些东西 1.app本身的功能比如录像,录音,更新都会产生文件,需要清理 2.app的默认缓存地址cache 二. 搞清楚要清理的文件夹位置 1.首先app自身的功能就要看自己把它放在了什么位置 2.默认缓存地址:getActivity().getExternalCacheDir(); 这个位置是在storage/emulated/0/Android/data/com.xxxxxapp/cache 三.代码功能

  • 关于Android的 DiskLruCache磁盘缓存机制原理

    目录 一.为什么用DiskLruCache 1.LruCache和DiskLruCache 2.为何使用DiskLruCache 二.DiskLruCache使用 1.添加依赖 2.创建DiskLruCache对象 3.添加 / 获取 缓存(一对一) 4.添加 / 获取 缓存(一对多) 三.源码分析 1.open() 2.rebuildJournal() 3.readJournal() 4.get() 5.validateKey 6.trimTOSize() 7.journalRebuildRe

  • Android 图片的三级缓存机制实例分析

    Android 图片的三级缓存机制实例分析 当我们获取图片的时候,如果不加以协调好图片的缓存,就会造成大流量,费流量应用,用户体验不好,影响后期发展.为此,我特地分享Android图片的三级缓存机制之从网络中获取图片,来优化应用,具体分三步进行: (1)从缓存中获取图片 (2)从本地的缓存目录中获取图片,并且获取到之后,放到缓存中 (3)从网络去下载图片,下载完成之后,保存到本地和放到缓存中 很好的协调这三层图片缓存就可以大幅度提升应用的性能和用户体验. 快速实现三级缓存的工具类ImageCac

  • Python代码块及缓存机制原理详解

    这篇文章主要介绍了Python代码块及缓存机制原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.相同的字符串在Python中地址相同 s1 = 'panda' s2 = 'panda' print(s1 == s2) #True print(id(s1) == id (s2)) #True 2.代码块: 所有的代码都需要依赖代码块执行. ​ 一个模块,一个函数,一个类,一个文件等都是一个代码块 ​ 交互式命令中, 一行就是一个代码块

  • Java包装类的缓存机制原理实例详解

    这篇文章主要介绍了Java包装类的缓存机制原理实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 java 包装类的缓存机制,是在Java 5中引入的一个有助于节省内存.提高性能的功能,只有在自动装箱时有效 Integer包装类 举个栗子: Integer a = 127; Integer b = 127; System.out.println(a == b); 这段代码输出的结果为true 使用自动装箱将基本类型转为封装类对象这个过程其实

  • Smarty缓存机制实例详解【三种缓存方式】

    本文实例讲述了Smarty缓存机制.分享给大家供大家参考,具体如下: Smarty模板引擎中强大的缓存机制,缓存机制有效减少了系统对服务器的压力,而这也是很多开发者喜欢Smarty的原因之一,附录中讲解了设置缓存及清除缓存的技巧方法(其中包含缓存集合方法). 一.Smarty缓存的几种方式 缓存机制中,分为全局缓存.部分缓存.局部缓存三种方式,后面会一一讲述,下面是缓存设置前,Smarty类方法基本目录设置如下: $smarty->Smarty(); $smarty->template_dir

  • Android 图片缓存机制的深入理解

    Android 图片缓存机制的深入理解 Android加载一张图片到用户界面是很简单的,但是当一次加载多张图片时,情况就变得复杂起来.很多情况下(像ListView.GridView或ViewPager等组件),屏幕上已显示的图片和即将滑动到当前屏幕上的图片数量基本上是没有限制的. 这些组件通过重用已经移除屏幕的子视图来将降低内存的使用,垃圾回收器也会及时释放那些已经不再使用的已下载的图片,这些都是很好的方法,但是为了保持一个流畅的.快速加载的用户界面,就应该避免当再次回到某个页面时而重新处理图

  • 从源代码分析Android Universal ImageLoader的缓存处理机制

    通过本文带大家一起看过UIL这个国内外大牛都追捧的图片缓存类库的缓存处理机制.看了UIL中的缓存实现,才发现其实这个东西不难,没有太多的进程调度,没有各种内存读取控制机制.没有各种异常处理.反正UIL中不单代码写的简单,连处理都简单.但是这个类库这么好用,又有这么多人用,那么非常有必要看看他是怎么实现的.先了解UIL中缓存流程的原理图. 原理示意图 主体有三个,分别是UI,缓存模块和数据源(网络).它们之间的关系如下: ① UI:请求数据,使用唯一的Key值索引Memory Cache中的Bit

  • Android中图片的三级缓存机制

    我们不能每次加载图片的时候都让用户从网络上下载,这样不仅浪费流量又会影响用户体验,所以Android中引入了图片的缓存这一操作机制. 原理: 首先根据图片的网络地址在网络上下载图片,将图片先缓存到内存缓存中,缓存到强引用中 也就是LruCache中.如果强引用中空间不足,就会将较早存储的图片对象驱逐到软引用(softReference)中存储,然后将图片缓存到文件(内部存储外部存储)中:读取图片的时候,先读取内存缓存,判断强引用中是否存在图片,如果强引用中存在,则直接读取,如果强引用中不存在,则

随机推荐