概述
DiskLruCache,是JakeWharton大神开源的作品,用于磁盘缓存,与LruCache内存缓存相对应,都是使用LRU算法。
获取DiskLruCache
因为DiskLruCache不是Android官方的,所以在Android SDK中找不到,但是得到官方的推荐。
获取方式一:
https://github.com/JakeWharton/DiskLruCache
获取方式二:
https://android.googlesource.com/platform/libcore/+/jb-mr2.0.0-release/luni/src/main/java/libcore/io/DiskLruCache.java
DiskLruCache的使用 打开缓存 1 2 3 4 5 6 7 8 public static DiskLruCache open (File directory, int appVersion, int valueCount, long maxSize) throws IOException
写入缓存 1 2 3 4 5 6 7 8 9 10 String key = generateKey(url); DiskLruCache.Editor editor = mDiskLruCache.edit(key); OutputStream os = editor.newOutputStream(0 ); editor.commit();
读取缓存 1 2 3 4 DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key); InputStream is = snapshot.getInputStream(0 );
journal文件解读 1 2 3 4 5 6 7 8 9 10 11 12 libcore.io.DiskLruCache 1 100 1 DIRTY 335 c4c6028171cfddfbaae1a9c313c52 CLEAN 335 c4c6028171cfddfbaae1a9c313c52 2342 REMOVE 335 c4c6028171cfddfbaae1a9c313c52 DIRTY 1 ab96a171faeeee38496d8b330771a7a CLEAN 1 ab96a171faeeee38496d8b330771a7a 1600 READ 335 c4c6028171cfddfbaae1a9c313c52 READ 3400330 d1dfc7f3f7f4b8d4d803dfcf6
journal文件头
第一行:固定字符串libcore.io.DiskLruCache
第二行:DiskLruCache的版本号,这个值恒为1。
第三行:应用程序的版本号。每当版本号改变,缓存路径下存储的所有数据都会被清空,因为DiskLruCache认为应用更新,所有的数据都应重新获取。
第四行:指每个key对应几个文件,一般为1。
第五行:空行
journal文件内容
DIRTY
:第六行以DIRTY前缀开始,后面跟着缓存文件的key,表示一个entry正在被写入。
CLEAN
:当写入成功,就会写入一条CLEAN记录,后面的数字记录文件的长度,如果一个key可以对应多个文件,那么就会有多个数字
REMOVE
:表示写入失败,或者调用remove(key)方法的时候都会写入一条REMOVE记录
READ
:表示一次读取记录
NOTE :当journal文件记录的操作次数达到2000时,就会触发重构journal的事件,来保证journal文件的大小始终在一个合理的范围内。
DiskLruCache源码解析 DiskLruCache#open 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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 cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); if (cache.journalFile.exists()) { try { cache.readJournal(); cache.processJournal(); cache.journalWriter = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(cache.journalFile, true ), Util.US_ASCII)); return cache; } catch (IOException journalIsCorrupt) { System.out .println("DiskLruCache " + directory + " is corrupt: " + journalIsCorrupt.getMessage() + ", removing" ); cache.delete(); } } directory.mkdirs(); cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); cache.rebuildJournal(); return cache; }
rebuildJournal(): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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 (DiskLruCache.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)); }
readJournal(): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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(); } finally { Util.closeQuietly(reader); } }
readJournalLine(): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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)) { lruEntries.remove(key); return ; } } else { key = line.substring(keyBegin, secondSpace); } DiskLruCache.Entry entry = lruEntries.get(key); if (entry == null ) { entry = new DiskLruCache.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 DiskLruCache.Editor(entry); } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { } else { throw new IOException("unexpected journal line: " + line); } }
processJournal(): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void processJournal () throws IOException { deleteIfExists(journalFileTmp); for (Iterator<DiskLruCache.Entry> i = lruEntries.values().iterator(); i.hasNext(); ) { DiskLruCache.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(); } } }
DiskLruCache#edit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private synchronized DiskLruCache.Editor edit (String key, long expectedSequenceNumber) throws IOException { checkNotClosed(); validateKey(key); DiskLruCache.Entry entry = lruEntries.get(key); if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) { return null ; } if (entry == null ) { entry = new DiskLruCache.Entry(key); lruEntries.put(key, entry); } else if (entry.currentEditor != null ) { return null ; } DiskLruCache.Editor editor = new DiskLruCache.Editor(entry); entry.currentEditor = editor; journalWriter.write(DIRTY + ' ' + key + '\n' ); journalWriter.flush(); return editor; }
Editor#newOutputStream 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public OutputStream newOutputStream (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); FileOutputStream outputStream; try { outputStream = new FileOutputStream(dirtyFile); } catch (FileNotFoundException e) { directory.mkdirs(); try { outputStream = new FileOutputStream(dirtyFile); } catch (FileNotFoundException e2) { return NULL_OUTPUT_STREAM; } } return new DiskLruCache.Editor.FaultHidingOutputStream(outputStream); } }
Editor#commit 1 2 3 4 5 6 7 8 9 10 public void commit () throws IOException { if (hasErrors) { completeEdit(this , false ); remove(entry.key); } else { completeEdit(this , true ); } committed = true ; }
Editor#completeEdit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 private synchronized void completeEdit (DiskLruCache.Editor editor, boolean success) throws IOException { DiskLruCache.Entry entry = editor.entry; if (entry.currentEditor != editor) { throw new IllegalStateException(); } 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); } }
DiskLruCache#get 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public synchronized DiskLruCache.Snapshot get (String key) throws IOException { checkNotClosed(); validateKey(key); DiskLruCache.Entry entry = lruEntries.get(key); if (entry == null ) { return null ; } if (!entry.readable) { 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) { 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 DiskLruCache.Snapshot(key, entry.sequenceNumber, ins, entry.lengths); }
参考链接
Android DiskLruCache 源码解析 硬盘缓存的绝佳方案
Android DiskLruCache完全解析,硬盘缓存的最佳方案
优雅的构建 Android 项目之磁盘缓存