本地缓存DiskLruCache源码分析
2019-09-26 本文已影响0人
李发糕
本文详细分析Glide及OKHttp所用的文件缓存类DiskLruCache~~ 分析顺序及笔者看代码的顺序
首先我们看一下注释
/*
* This cache uses a journal file named "journal". A typical journal file
* looks like this:
* 此缓存使用一个叫journal的日志文件,格式如下
*
* libcore.io.DiskLruCache //头部标示
* 1 //缓存版本
* 100 //应用版本
* 2 //valuecount 及缓存中一个缓存包含几个文件 比如Glide只有一个图片文件,而OKHttp有两个 一个存header,一个存body
* //空行
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
*
* 之后的每一行代表对一个缓存entry的状态记录 状态 key 可选的详细状态值
* 如下状态:
* o DIRTY 状态 代表一个entry正在被创建或者更新 每一个成功的DIRTY操作会紧跟着一个CLEAN或者是REMOVE
* 操作。如果没有跟着匹配的CLEAN或者REMOVE操作,代表这是个临时文件,可能需要删除
* o CLEAN 状态 代表一个缓存entry被成功发布了并且可读,后面会拼接该缓存每个文件的长度
* o READ 读LRU
* o REMOVE entry被移除了
*
* 日志文件附加到缓存操作发生时。有时可能会因删除冗余行而受到压缩。 压缩期间将使用名为“journal.tmp”的临时 * 文件; 如果在打开缓存时该文件存在,则应删除该文件。
*/
注释中多次提到了entry,实际上这也是我们外部调用时存取的入口,我们看一下
private final class Entry {
private final String key;//key
/** Lengths of this entry's files. */
private final long[] lengths;//缓存文件长度数组
/** Memoized File objects for this entry to avoid char[] allocations. */
File[] cleanFiles;//完成写入的文件
File[] dirtyFiles;//正在写入的文件
/** 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;//当前的editor,先不管
/** The sequence number of the most recently committed edit to this entry. */
private long sequenceNumber;//最近提交编辑给这个entry的序列号
//下面看一下构造方法
private Entry(String key) {
this.key = key;
this.lengths = new long[valueCount];
cleanFiles = new File[valueCount];
dirtyFiles = new File[valueCount];
// The names are repetitive so re-use the same builder to avoid allocations.
StringBuilder fileBuilder = new StringBuilder(key).append('.');
int truncateTo = fileBuilder.length();
for (int i = 0; i < valueCount; i++) {
fileBuilder.append(i);
cleanFiles[i] = new File(directory, fileBuilder.toString());//获取对应的文件引用
fileBuilder.append(".tmp");
dirtyFiles[i] = new File(directory, fileBuilder.toString());//获取临时文件引用
fileBuilder.setLength(truncateTo);//这里可以一个builder在循环中多次利用,相当于删除了之前拼接的内容
}
}
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 {//设置长度给lengths数组
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) {//获取cleanFile的引用
return cleanFiles[i];
}
public File getDirtyFile(int i) {//获取dirtyFile的引用
return dirtyFiles[i];
}
}
可以看到Entry中持有了我们缓存文件的引用,但是并没有get put 方法,却有一个Editor对象,顾名思义,编辑器。那么我们看一下这个Eidtor是干啥的
public final class Editor {
private final Entry entry;//这里可以看到,编辑器也持有Entry的对象,这个就是它正在编辑的对象
private final boolean[] written;//缓存中文件是否可写
private boolean committed;
private Editor(Entry entry) {
this.entry = entry;
this.written = (entry.readable) ? null : new boolean[valueCount];
}
//获取读文件的输入流 防止CLEAN文件在这里被写,所以只能返回输入流
private 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));//如果存在并且可读,返回clean文件的输入流
} catch (FileNotFoundException e) {
return null;
}
}
}
。。。
//获取可写的File
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);//返回写入中的dirtyFile
if (!directory.exists()) {
directory.mkdirs();
}
return dirtyFile;
}
}
/** Sets the value at {@code index} to {@code value}. */
public void set(int index, String value) throws IOException {
Writer writer = null;
try {
OutputStream os = new FileOutputStream(getFile(index));
writer = new OutputStreamWriter(os, Util.UTF_8);//写入dirty文件
writer.write(value);
} finally {
Util.closeQuietly(writer);
}
}
/**
* Commits this edit so it is visible to readers. This releases the
* edit lock so another edit may be started on the same key.
*/
public void commit() throws IOException {//提交
// The object using this Editor must catch and handle any errors
// during the write. If there is an error and they call commit
// anyway, we will assume whatever they managed to write was valid.
// Normally they should call abort.
completeEdit(this, true);//提交编辑
committed = true;//已经提交
}
/**
* Aborts this edit. This releases the edit lock so another edit may be
* started on the same key.
*/
public void abort() throws IOException {//中断
completeEdit(this, false);
}
public void abortUnlessCommitted() {
if (!committed) {
try {
abort();
} catch (IOException ignored) {
}
}
}
}
下面我们看一下上面提及的方法
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()) {//不存在dirty文件,那就没必要修改了
editor.abort();//终止这次修改
return;
}
}
}
for (int i = 0; i < valueCount; i++) {//循环这个缓存的文件
File dirty = entry.getDirtyFile(i);
if (success) {//成功
if (dirty.exists()) {//存在dirty
File clean = entry.getCleanFile(i);
dirty.renameTo(clean);//使用dirty替换旧的clean文件
long oldLength = entry.lengths[i];
long newLength = clean.length();
entry.lengths[i] = newLength;
size = size - oldLength + newLength;//修改长度
}
} else {
deleteIfExists(dirty);//失败,删除dirty
}
}
redundantOpCount++;//计数器++ ,暂时不知道他是啥没关系
entry.currentEditor = null; //置空编辑器 提交过了就不能再次编辑了
if (entry.readable | success) {//如果是提交并成功
entry.readable = true;
journalWriter.append(CLEAN);
journalWriter.append(' ');
journalWriter.append(entry.key);
journalWriter.append(entry.getLengths());
journalWriter.append('\n');
if (success) {
entry.sequenceNumber = nextSequenceNumber++;//操作序列号++
}
} else {//否则说明提交失败了,记录删除dirty了
lruEntries.remove(entry.key);
journalWriter.append(REMOVE);
journalWriter.append(' ');
journalWriter.append(entry.key);
journalWriter.append('\n');
}
journalWriter.flush();//flushwriter
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);//整理重构日志记录
}
}
那么我们在外部如何获取Editor来操作呢
首先LruCache提供了一个get方法
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);//entry序列号,这个方法不需要
}
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);//这就是一个entry的lru内存缓存
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
return null; // Value is stale.
}
if (entry == null) {//新的key
entry = new Entry(key);//新建一个entry
lruEntries.put(key, entry);//放入缓存
} else if (entry.currentEditor != null) {//这个entry正在被编辑
return null; // return
}
Editor editor = new Editor(entry);//实例用于操作该entry的editor
entry.currentEditor = editor;//绑定
journalWriter.append(DIRTY);//写入dirty日志
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
journalWriter.flush();
return editor;
}
另外还有一个内部类Value我们看一下
public final class Value {//当前entry的一个快照
private final String key;
private final long sequenceNumber;
private final long[] lengths;
private final File[] files;
private Value(String key, long sequenceNumber, File[] files, long[] lengths) {
this.key = key;
this.sequenceNumber = sequenceNumber;
this.files = files;
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);//直接编辑sequenceNumber对应的entry
}
public File getFile(int index) {
return files[index];
}
/** Returns the string value for {@code index}. */
public String getString(int index) throws IOException {
InputStream is = new FileInputStream(files[index]);
return inputStreamToString(is);
}
/** Returns the byte length of the value for {@code index}. */
public long getLength(int index) {
return lengths[index];
}
}
最后我们看一下整体Lrucache的流程
public final class DiskLruCache implements Closeable {//首先看一下全局变量
static final String JOURNAL_FILE = "journal"; //日志文件名
static final String JOURNAL_FILE_TEMP = "journal.tmp"; //日志临时文件名
static final String JOURNAL_FILE_BACKUP = "journal.bkp"; //日志备份文件名
static final String MAGIC = "libcore.io.DiskLruCache"; //日志文件头
static final String VERSION_1 = "1"; //版本号
static final long ANY_SEQUENCE_NUMBER = -1; //空序列号
private static final String CLEAN = "CLEAN";
private static final String DIRTY = "DIRTY";
private static final String REMOVE = "REMOVE";
private static final String READ = "READ";
private final File directory; //缓存所在文件夹
private final File journalFile; //日志文件
private final File journalFileTmp;
private final File journalFileBackup;
private final int appVersion;
private long maxSize;//日志文件最大值
private final int valueCount;//当前缓存中一个缓存包含几个文件
private long size = 0;//日志文件当前长度
private Writer journalWriter;//日志文件writer
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<String, Entry>(0, 0.75f, true);//缓存文件的引用在内存中的缓存 使用lru
private int redundantOpCount;//多余的操作的数量
/**
* To differentiate between old and current snapshots, each entry is given
* a sequence number each time an edit is committed. A snapshot is stale if
* its sequence number is not equal to its entry's sequence number.
*/
private long nextSequenceNumber = 0;//序列号,每次提交成功会自增,用于判断当前快照是否过期
/** 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>() {//cleanup的操作
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();//压缩尺寸
if (journalRebuildRequired()) {//还需要rebuild
rebuildJournal(); //rebuild
redundantOpCount = 0; //rebuild完了,多余的操作记录数置空
}
}
return null;
}
};
。。。
// 根据文件夹及版本号,maxsize等打开一个缓存
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
throws IOException {
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 cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);//实例一个DiskLruCache
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;//返回
}
//下面先看看读取日志的方法
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 {
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();//rebuild
} else {
journalWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(journalFile, true), Util.US_ASCII));//获取对日志文件的写入writer
}
} finally {
Util.closeQuietly(reader);
}
}
//下面我们看看单行读取的时候
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {//如果调用了remove 移除
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
String[] parts = line.substring(secondSpace + 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
}
/**
* Computes the initial size and collects garbage as a part of opening the
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
*/
private void processJournal() throws IOException {
deleteIfExists(journalFileTmp);
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
Entry entry = i.next();
if (entry.currentEditor == null) {
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];
}
} else {
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
deleteIfExists(entry.getCleanFile(t));
deleteIfExists(entry.getDirtyFile(t));
}
i.remove();
}
}
}
/**
* 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();
}
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();
journalWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
}
private static void deleteIfExists(File file) throws IOException {
if (file.exists() && !file.delete()) {
throw new IOException();
}
}
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
if (deleteDestination) {
deleteIfExists(to);
}
if (!from.renameTo(to)) {
throw new IOException();
}
}
/**
* 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.
*/
public synchronized Value get(String key) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
for (File file : entry.cleanFiles) {
// A file must have been deleted manually!
if (!file.exists()) {
return null;
}
}
redundantOpCount++;
journalWriter.append(READ);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
}
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
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;
}
/** Returns the directory where this cache stores its data. */
public File getDirectory() {
return directory;
}
/**
* Returns the maximum number of bytes that this cache should use to store
* its data.
*/
public synchronized long getMaxSize() {
return maxSize;
}
/**
* Changes the maximum number of bytes the cache can store and queues a job
* to trim the existing store, if necessary.
*/
public synchronized void setMaxSize(long maxSize) {
this.maxSize = maxSize;
executorService.submit(cleanupCallable);
}
/**
* Returns the number of bytes currently being used to store the values in
* this cache. This may be greater than the max size if a background
* deletion is pending.
*/
public synchronized long size() {
return size;
}
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.append(CLEAN);
journalWriter.append(' ');
journalWriter.append(entry.key);
journalWriter.append(entry.getLengths());
journalWriter.append('\n');
if (success) {
entry.sequenceNumber = nextSequenceNumber++;
}
} else {
lruEntries.remove(entry.key);
journalWriter.append(REMOVE);
journalWriter.append(' ');
journalWriter.append(entry.key);
journalWriter.append('\n');
}
journalWriter.flush();
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
/**
* 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();
}
/**
* Drops the entry for {@code key} if it exists and can be removed. Entries
* actively being edited cannot be removed.
*
* @return true if an entry was removed.
*/
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
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);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
/** Returns true if this cache has been closed. */
public synchronized boolean isClosed() {
return journalWriter == null;
}
private void checkNotClosed() {
if (journalWriter == null) {
throw new IllegalStateException("cache is closed");
}
}
/** Force buffered operations to the filesystem. */
public synchronized void flush() throws IOException {
checkNotClosed();
trimToSize();
journalWriter.flush();
}
/** Closes this cache. Stored values will remain on the filesystem. */
public synchronized void close() throws IOException {
if (journalWriter == null) {
return; // Already closed.
}
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
if (entry.currentEditor != null) {
entry.currentEditor.abort();
}
}
trimToSize();
journalWriter.close();
journalWriter = null;
}
private void trimToSize() throws IOException {
while (size > maxSize) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
/**
* Closes the cache and deletes all of its stored values. This will delete
* all files in the cache directory including files that weren't created by
* the cache.
*/
public void delete() throws IOException {
close();
Util.deleteContents(directory);
}
private static String inputStreamToString(InputStream in) throws IOException {
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
}
}