DiskLruCache 硬盘缓存,非Google官方编写,但获得官方承认, 只需要下载下来放到项目中就行。
journal文件
这个日志文件,关系着DiskLruCache的正常使用,里面记录了每条缓存,下面看看里面信息
第一行是固定的字符串,第二行是DiskLruCache的版本号,这个值为1,第三行是APP的版本号,每当更新版本时会清除缓存,第四行是valueCount的值,在open时传入,一般为1,第五行是个空行。在下面就是缓存的信息
分别有几种前缀,DIRTY脏数据,当我们调用DiskLruCache的edit()方法时就会写入一个DIRTY,后面是缓存的key;CLEAN,当调用了commit方法后,会写入一条CLEAN;REMOVE,当调用absort方法,写入缓存失败,会产生一条REMOVE;READ,当读取一条缓存时会写入一条READ。
open获得实例
DiskLruCache 不是new出来的,而是通过一个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");
}
//根据参数创建对象赋值
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
//判断日志文件是否存在
if (cache.journalFile.exists()) {
try {
//读取日志文件
cache.readJournal();
cache.processJournal();
//写日志文件的Writer
cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
IO_BUFFER_SIZE);
return cache;
} catch (IOException journalIsCorrupt) {
// System.logW("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;
}
主要就是创建对象,读取或者创建日志文件。
看看void readJournal()
private void readJournal() throws IOException {
InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);
try {
//这一段一般是文件头的信息
String magic = readAsciiLine(in);
String version = readAsciiLine(in);
String appVersionString = readAsciiLine(in);
String valueCountString = readAsciiLine(in);
String blank = readAsciiLine(in);
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 + "]");
}
//开始读日志信息
while (true) {
try {
//解析每一行的具体信息
readJournalLine(readAsciiLine(in));
} catch (EOFException endOfJournal) {
break;
}
}
} finally {
//最后关闭输入流
closeQuietly(in);
}
}
这个函数就是读取日志文件,前几行是日志的一些头信息,后面while循环解析日志,就是缓存的信息,解析出来放到了一个hash链表里面存储。
void rebuildJournal()
private synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) {
journalWriter.close();
}
//writer指向的journalFileTmp
Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
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');
}
}
writer.close();
journalFileTmp.renameTo(journalFile);
journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
}
主要就是创建journal
写入缓存
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
//校验journalWriter是否为空
checkNotClosed();
//校验key的合法性
validateKey(key);
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
&& (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
return null; // snapshot is stale
}
//如果entry为空,创建一个并加入map里面
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;
// 写一条DIRTY
journalWriter.write(DIRTY + ' ' + key + '\n');
journalWriter.flush();
return editor;
}
获得输出流
public OutputStream newOutputStream(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
}
}
commit()操作
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
remove(entry.key); // the previous entry is stale
} else {
completeEdit(this, true);
}
}
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++) {
//不存在dirty类型的,则停止,抛出异常
if (!entry.getDirtyFile(i).exists()) {
editor.abort();
throw new IllegalStateException("edit didn't create file " + i);
}
}
}
for (int i = 0; i < valueCount; i++) {
//获得dirty类型的File
File dirty = entry.getDirtyFile(i);
if (success) {
if (dirty.exists()) {
//dirty转化为clean
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');
}
//当缓存过大时,重新创建
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
获取缓存
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!
return null;
}
redundantOpCount++;
//取出缓存后添加一条READ
journalWriter.append(READ + ' ' + key + '\n');
//检查是否需要重构journal
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Snapshot(key, entry.sequenceNumber, ins);
}
返回SnapShot对象,通过这个对象就可以获得缓存的输入流。
其他一些方法
remove(String key)
移除特定的缓存,一般不使用,因为当缓存到达一定大小时,会去自定清理长期没用过的缓存
flush
同步内存的操作到journal文件中
delete()
删除所有缓存数据
close()
关闭DiskLruCache
小Demo
通过网络获取一张图片,缓存起来,点击按钮,通过缓存加载图片。