LRU分析
一种缓存策略。根据最近使用频率,最近最少使用的也认为将来不怎么使用,所以缓存也就越容易清除。
一般LinkedHashMap作为实现,实际上通过构造函数,设置true则每一次操作都会自动将key移动到末尾。
private final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<String, Entry>(0, 0.75f, true);
这样一个LRU策略很轻松的就实现了。至于DiskLruCache很明显是缓存到本地文件,事实上内存Map保存的value也只是文件名字,而不是文件内容
数据结构分析
private final class Entry { private final String key; /** Lengths of this entry's files. */ private final long[] lengths; /** Memoized File objects for this entry to avoid char[] allocations. */ File[] cleanFiles;//待dirtyFile写入再重命名为此文件,成功即认为写入成功 File[] dirtyFiles;//一般是tmp文件,先操作此文件 /** True if this entry has ever been published. */ private boolean readable;//成功写入即可认为是true /** The ongoing edit or null if this entry is not being edited. */ private Editor currentEditor;//当DIRTY标志则需要赋值一个操作对象 /** The sequence number of the most recently committed edit to this entry. */ private long sequenceNumber;
一般写入操作,先认为是dirty类型,操作的也是dirty文件,然后此文件重命名为clean文件则认为操作成功
journal文件
所有这些操作都会记录在journal文件,这个文件类似于日志
DIRTY 表示初始put操作,例如添加一条缓存,那么此时是DIRTY,不知道结果怎么样
put时候,成功。那么CLEAN标志
put时候,失败,REMOVE标志
get时候,READ标志
既然是磁盘缓存,那么每一次初始化需要加载日志,然后根据日志操作,内存映射相应的填充。
这里一个很重要的概念是DiskLRUCache不存储真实的文件内容,只是存储文件名字。
解析日志文件journal文件
解析需要看看当前文件magic,版本等,然后校验合法再依次解析每一个操作
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 + "]"); }
解析每一行操作
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);//REMOVE代表写失败,所以内存需要删除此值 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);//CLEAN代表成功写入,设置readable为true } 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); } }
做一些预处理
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(); } }
读取缓存逻辑
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); }//返回对应clean文件 return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths); }
分析一下completeEdit
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) {//需要成功回写,如果不可展示应该是dirty文件存在 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);//dirty->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');//成功则CLEAN标志 if (success) { entry.sequenceNumber = nextSequenceNumber++; } } else { lruEntries.remove(entry.key); journalWriter.append(REMOVE); journalWriter.append(' '); journalWriter.append(entry.key); journalWriter.append('\n');//失败则REMOVE标志 } journalWriter.flush(); if (size > maxSize || journalRebuildRequired()) { executorService.submit(cleanupCallable); } }
成功提交与失败回退
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; }
public void abort() throws IOException { completeEdit(this, false); }
作者:enjoycc97
链接:https://www.jianshu.com/p/f6b3554505aa
点击查看更多内容
为 TA 点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦