库地址:https://github.com/JakeWharton/DiskLruCache
简介:DiskLruCache一个硬盘缓存管理工具,为了保持原有空间的大小,其算法是最近最少使用算法。它把最近使用的对象用“强引用”存储在LinkedHashMap.并且把最近最少使用的对象在缓存值达到预设定值之前就从磁盘中移除
其构造函数如下:
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;//缓存路径
this.appVersion = appVersion;//app版本
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;//每个key对应的资源数
this.maxSize = maxSize;//最大缓存值
}
三个文件描述文件操作日志:
static final String JOURNAL_FILE = "journal"; //操作日志
static final String JOURNAL_FILE_TEMP = "journal.tmp";//操作日志缓存
static final String JOURNAL_FILE_BACKUP = "journal.bkp";//操作日志备份
用LinkedHashMap描述 资源对应关系
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<String, Entry>(0, 0.75f, true);
Entry对象描述 资源流入口,可根据key获得映射Entry
private final class Entry {
private final String key;
/** Lengths of this entry's files. */
private final long[] lengths;
/** True if this entry has ever been published. */
private boolean readable;
/** The ongoing edit or null if this entry is not being edited. */
private Editor currentEditor;
/** 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];
}
...
}
每个Entry有对应的Editor 负责资源的流的处理
public final class Editor {
private final Entry entry;
private final boolean[] written;
private boolean hasErrors;
private boolean committed;
private Editor(Entry entry) {
this.entry = entry;
this.written = (entry.readable) ? null : new boolean[valueCount];
}
...
}
每个Entry 对应多个value资源流,资源流个数由DiskLruCache构造函数参数valueCount 决定,资源名为:
private final class Entry{
...
public File getCleanFile(int i) {
return new File(directory, key + "." + i);
}
public File getDirtyFile(int i) {
return new File(directory, key + "." + i + ".tmp");
}
}
Editor 写流 通过 newOutputStream(int index)方法 写数据到Entry的getDirtyFile(int index)文件,此时为缓存文件,必须调用commit()方法之后,才算真正存储
public final class 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);
}
}
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
remove(entry.key); // The previous entry is stale.
} else {
completeEdit(this, true);
}
committed = true;
}
...
}
commit()方法中调用completeEdit()完成资源文件重命名(缓存转实际),此时如果缓存小已超过指定值那么将会调用线程池中开启线程调用trimToSize();
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
...
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);
}
}
...
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
trimToSize会从LinkedHashMap中得到需要移除资源的信息,然后remove(); remove删除资源文件,并重新计算size;
private void trimToSize() throws IOException {
while (size > maxSize) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
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()) {
executorService.submit(cleanupCallable);
}
return true;
}
Editor 读流 通过 newInputStream(int index)方法 从对应Entry的getCleanFile(int index)读文件数据
public final class Editor {
public InputStream newInputStream(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
return null;
}
try {
return new FileInputStream(entry.getCleanFile(index));
} catch (FileNotFoundException e) {
return null;
}
}
}
}
另外说明一点:缓存size的计算由各个file的length()方法得到字节数累加而成.根据文件操作的不同,size要跟随变化