DiskLurCache
使用
打開緩存
-
打開緩存函數(shù)
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
? open()方法接收四個(gè)參數(shù)怪瓶,第一個(gè)參數(shù)指定的是數(shù)據(jù)的緩存地址,第二個(gè)參數(shù)指定當(dāng)前應(yīng)用程序的版本號(hào),第三個(gè)參數(shù)指定同一個(gè)key可以對(duì)應(yīng)多少個(gè)緩存文件堤尾,基本都是傳1语御,第四個(gè)參數(shù)指定最多可以緩存多少字節(jié)的數(shù)據(jù)。
-
實(shí)際調(diào)用
public File getDiskCacheDir(Context context, String uniqueName) { String cachePath; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { cachePath = context.getExternalCacheDir().getPath(); } else { cachePath = context.getCacheDir().getPath(); } return new File(cachePath + File.separator + uniqueName); } public int getAppVersion(Context context) { try { PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); return info.versionCode; } catch (NameNotFoundException e) { e.printStackTrace(); } return 1; } //調(diào)用open 函數(shù), 這里的 open 函數(shù), 當(dāng)版本號(hào)改變后, //appVersionString, valueCountString 改變的時(shí)候, 會(huì)直接 報(bào) IO異常 DiskLruCache mDiskLruCache = null; try { File cacheDir = getDiskCacheDir(context, "bitmap"); if (!cacheDir.exists()) { cacheDir.mkdirs(); } mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024); } catch (IOException e) { e.printStackTrace(); }
//private void readJournal() throws IOException 函數(shù).
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 + "]");
}
寫入緩存
-
下載一張圖片
//調(diào)用 URL 系在一張圖片, 并寫入 outputStream 中. private boolean downloadUrlToStream(String urlString, OutputStream outputStream) { HttpURLConnection urlConnection = null; BufferedOutputStream out = null; BufferedInputStream in = null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024); out = new BufferedOutputStream(outputStream, 8 * 1024); int b; while ((b = in.read()) != -1) { out.write(b); } return true; } catch (final IOException e) { e.printStackTrace(); } finally { if (urlConnection != null) { urlConnection.disconnect(); } try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (final IOException e) { e.printStackTrace(); } } return false; }
?
-
根據(jù) URL 生成 MD5值, 唯一標(biāo)識(shí), 當(dāng)作內(nèi)部的 LURCache List的鍵
public String hashKeyForDisk(String key) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(key.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(key.hashCode()); } return cacheKey; } private String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]); if (hex.length() == 1) { sb.append('0'); } sb.append(hex); } return sb.toString(); }
-
調(diào)用 edit(key) 獲取 Editor 對(duì)象
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg"; String key = hashKeyForDisk(imageUrl); DiskLruCache.Editor editor = mDiskLruCache.edit(key); //這里的 editor 里面可以生成一個(gè) 關(guān)于 dirty 文件的 output 對(duì)象, 并重寫了 //輸出流的 write()函數(shù), 一旦寫操作報(bào) IO 異常, 會(huì)將 Entry 中的 hasError 置為 true, //在 commit 的時(shí)候會(huì)調(diào)用 commitEdit(false), 將 dirty 刪除掉, //理想情況下是成功的, 那么會(huì)將文件保存為 clean 文件, 并記錄一行 DIRTY操作, //下載圖片使用的 outputStream 是editor 的, 也就是 指向 dirty 的 outputStream. //在調(diào)用 commit/abort 函數(shù)時(shí),會(huì)將dirty文件轉(zhuǎn)換為clean文件,或者刪除掉. new Thread(new Runnable() { @Override public void run() { try { String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg"; String key = hashKeyForDisk(imageUrl); DiskLruCache.Editor editor = mDiskLruCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(0); if (downloadUrlToStream(imageUrl, outputStream)) { editor.commit(); } else { editor.abort(); } } //檢查當(dāng)前 存儲(chǔ)的size 是否大于設(shè)置的maxSize, //大于, 將刪除 LUR 中原本不常用的 文件, 直到 size < maxSize. mDiskLruCache.flush(); } catch (IOException e) { e.printStackTrace(); } } }).start();
讀取緩存
-
調(diào)用函數(shù)
public synchronized Snapshot get(String key) throws IOException
-
根據(jù) URL 生成的KEY 去獲取對(duì)應(yīng)的文件, 獲取 SnapShot 對(duì)象.
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg"; String key = hashKeyForDisk(imageUrl); DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
-
調(diào)用 SnapShot 對(duì)象里面輸入流
輸入流 指向的是 KEY 對(duì)應(yīng)的 CLEAN 文件 的 InputStrean, 并且在 SnapSnot 中保存的是一個(gè) inputStream 數(shù)組, 數(shù)組的長(zhǎng)度是 valueCount(一個(gè)KEY 對(duì)應(yīng)幾個(gè)文件) 的大小,
每個(gè) inputStream[] 保存的是對(duì)應(yīng)的下標(biāo) 文件, 具體的 獲取文件的函數(shù)在 Entry 中, 調(diào)用 getCleanFile(index) 函數(shù)獲取.
? 這里調(diào)用getInputStream(0), 將 inputStream 轉(zhuǎn)換為 Bitmap 并顯示出來.
try { String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg"; String key = hashKeyForDisk(imageUrl); DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) { InputStream is = snapShot.getInputStream(0); Bitmap bitmap = BitmapFactory.decodeStream(is); mImage.setImageBitmap(bitmap); } } catch (IOException e) { e.printStackTrace(); }
移除緩存
mDiskLruCache.remove(key); 調(diào)用 remove 函數(shù), 關(guān)鍵判斷為 key 值來刪除
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
mDiskLruCache.remove(key);
} catch (IOException e) {
e.printStackTrace();
}
將會(huì)在 journal 文件中寫入一行 REMOVE 操作, 并將 redundantOpCount++
源碼解析
概述
DiskLurCache 涉及到一個(gè) journal 的文件, 這個(gè)文件保存 CLEAN, DIRTY, REMOVE, READ 操作
-
初始化一個(gè) DiskLurCache 對(duì)象, 需要調(diào)用 open 函數(shù)
DiskLruCache.open(directory, appVersion, valueCount, maxSize) ;
-
關(guān)于寫操作
String key = generateKey(url); DiskLruCache.Editor editor = mDiskLruCache.edit(key); OuputStream os = editor.newOutputStream(0); os.write(...) os.falsh(); //提交寫操作, 將之前寫的 temp 文件保存為 clean 文件, //當(dāng)之前寫操作出現(xiàn)錯(cuò)誤的時(shí)候, 會(huì)將文件刪除, 并將 KEY 從 lur 中刪除掉. editor.commit();
-
關(guān)于讀操作
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) { InputStream is = snapShot.getInputStream(0); } Bitmap bitmap = BitmapFactory.decodeStream(is); imageView.setBitmap(bitmap); //關(guān)閉所有的 inputStream. snapShot.close();
journal 文件
journal文件你打開以后呢,是這個(gè)格式切黔;
libcore.io.DiskLruCache
1
1
1
DIRTY c3bac86f2e7a291a1a200b853835b664
CLEAN c3bac86f2e7a291a1a200b853835b664 4698
READ c3bac86f2e7a291a1a200b853835b664
DIRTY c59f9eec4b616dc6682c7fa8bd1e061f
CLEAN c59f9eec4b616dc6682c7fa8bd1e061f 4698
READ c59f9eec4b616dc6682c7fa8bd1e061f
DIRTY be8bdac81c12a08e15988555d85dfd2b
CLEAN be8bdac81c12a08e15988555d85dfd2b 99
READ be8bdac81c12a08e15988555d85dfd2b
DIRTY 536788f4dbdffeecfbb8f350a941eea3
REMOVE 536788f4dbdffeecfbb8f350a941eea3
首先看前五行:ok板乙,以上5行可以稱為該文件的文件頭是偷,DiskLruCache初始化的時(shí)候,如果該文件存在需要校驗(yàn)該文件頭募逞。
DiskLruCache初始化的時(shí)候蛋铆,如果該文件存在需要校驗(yàn)該文件頭。
- 第一行固定字符串
libcore.io.DiskLruCache
- 第二行DiskLruCache的版本號(hào)放接,源碼中為常量1
- 第三行為你的app的版本號(hào)刺啦,當(dāng)然這個(gè)是你自己傳入指定的
- 第四行指每個(gè)key對(duì)應(yīng)幾個(gè)文件,一般為1
- 第五行纠脾,空行
操作記錄:
- DIRTY 表示一個(gè)entry正在被寫入(其實(shí)就是把文件的OutputStream交給你了)玛瘸。那么寫入分兩種情況,如果成功會(huì)緊接著寫入一行CLEAN的記錄苟蹈;如果失敗糊渊,會(huì)增加一行REMOVE記錄。
- REMOVE除了上述的情況呢慧脱,當(dāng)你自己手動(dòng)調(diào)用remove(key)方法的時(shí)候也會(huì)寫入一條REMOVE記錄渺绒。
- READ就是說明有一次讀取的記錄。
- 每個(gè)CLEAN的后面還記錄了文件的長(zhǎng)度菱鸥,注意可能會(huì)一個(gè)key對(duì)應(yīng)多個(gè)文件宗兼,那么就會(huì)有多個(gè)數(shù)字(參照文件頭第四行)。
DiskLruCache#open
-
open 函數(shù)
/** * 打開緩存在文件夾中, 如果不存在就創(chuàng)建. * Opens the cache in {@code directory}, creating a cache if none exists * there. * * @param directory a writable directory 緩存目錄 * @param valueCount the number of values per cache entry. Must be positive. 每個(gè)緩存條目的值數(shù)量. 每個(gè) KEY 對(duì)應(yīng)的文件 * @param maxSize the maximum number of bytes this cache should use to store 用于存儲(chǔ)的最大字節(jié)數(shù). * @throws IOException if reading or writing the cache directory fails 當(dāng)寫文件和度文件失敗, 會(huì)拋出異常. */ //創(chuàng)建 DiskLruCache 對(duì)象, 并初始化文件存放的地址. 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"); } //查找 bkp 文件是否存在, 不存在 // If a bkp file exists, use it instead. 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. //即存在 bkp 文件又存在 journal 文件, 刪除backup 文件, //存在 bkp 文件,但是不存在 journal文件, 將文件重命名. 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); //根據(jù)文件夾信息, 創(chuàng)建對(duì)應(yīng)的源信息, 和backup , 和臨時(shí)操作的文件. 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; }
-
重新創(chuàng)建 journal 文件 (1. 文件不存在, 2. 文件存在, 但是多余的操作超過 2000, 為了保證 journal 文件的大小, 會(huì)重新生成文件.)
/** * 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(); } //新將數(shù)據(jù)寫到 tmp 文件中. 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"); //重新寫文件, REMOVE, READ 操作會(huì)被干光, 重新寫文件. for (Entry entry : lruEntries.values()) { //判斷entry 是否是臟數(shù)據(jù)的存在. 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()) { //在清除 REMOVE, READ 操作的時(shí)候, 如果 之前存在 journalFile, 那么將文件保存為 Backup 文件, 并將源文件刪除. renameTo(journalFile, journalFileBackup, true); } //將 tmp 文件重新保存為 journalFile 文件, 但是不刪除 tmp 文件. renameTo(journalFileTmp, journalFile, false); //在轉(zhuǎn)換成功后, 將 備份文件也刪除掉, 在 DiskLurCache 的每關(guān)于文件的操作都會(huì)將 IO 異常拋出去, //這里就是當(dāng) renameTo() 這個(gè)函數(shù)被拋出了 IO 異常的時(shí)候備份文件不會(huì)被刪除掉. journalFileBackup.delete(); journalWriter = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII)); }
-
初始化的時(shí)候, 文件存在, 讀取文件行, 并保證 lur 中保存的記錄是只有 CLEAN, 而沒有 REMOVE/ DIRTY 操作的 KEY - ENTRY
//只會(huì)在初始化的時(shí)候被調(diào)用 open() 函數(shù)的時(shí)候才會(huì)被調(diào)用. /** * 1. 通過文件頭來檢測(cè)是否是 journal 文件, 如果不是, 直接報(bào)IO 異常, * 2. 對(duì)文件進(jìn)行while 循環(huán), 一直跑到捕獲文件尾異常, * 2.1. while 循環(huán)中會(huì)將 標(biāo)簽為 CLEAN 標(biāo)志的標(biāo)簽的KEY 添加到 LUR 數(shù)組中去, * 但是當(dāng)在輪詢中碰到 Remove 的操作標(biāo)簽, 會(huì)將 對(duì)應(yīng)的 KEY 從原本的 LUR 數(shù)組中移除, * * 2.3. 判斷3個(gè)狀態(tài), REMOVE, CLEAN, DIRTY, * REMOVE: 會(huì)刪除在 LUR 中的 KEY 值. * CLEAN: 生成一個(gè) Entry(不管是不是空的), * 設(shè)置 currentEditor = null, * 設(shè)置 readable = true (可讀) * 設(shè)置 lengths, 也是通過空格來區(qū)分的. * * DIRTY: 臟數(shù)據(jù), 如果文件是臟數(shù)據(jù)(正在操作)時(shí). 分配一個(gè)新的 Editor(關(guān)于 entry的 Editor(文件流)) * REMOVE, CLEAR, DIRTY, READ, 和KEY 之間都有空格, 他們之間的判斷第一個(gè)是名稱, 第二個(gè)是空格的數(shù)量. * * 2.4. 統(tǒng)計(jì)當(dāng)前文件中多余的操作次數(shù): * 文本的行數(shù) - LUR.size() = 多余操作次數(shù). * * 2.5. 判斷文件的讀寫是否是異常停止, 文件未讀到末尾, 則調(diào)用 reBuildJournal() 函數(shù). 重寫生成 journal 文件. * * 2.6. journalWriter 初始化 journalWriter, 寫字段到 journal文件中的 輸出流. * * @throws IOException */ 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) { //捕獲 crash 來跳出循環(huán). break; } } //多余的操作次數(shù). redundantOpCount = lineCount - lruEntries.size(); // If we ended on a truncated line, rebuild the journal before appending to it. //函數(shù)執(zhí)行錯(cuò)誤, 未結(jié)束, 但是報(bào)了 EOFException 錯(cuò)誤. if (reader.hasUnterminatedLine()) { rebuildJournal(); } else { //初始化 write. journalWriter = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(journalFile, true), Util.US_ASCII)); } } finally { //關(guān)閉 reader 流. Util.closeQuietly(reader); } } private void readJournalLine(String line) throws IOException { //切割字符串, 準(zhǔn)備判斷關(guān)于 KEY 值的狀態(tài), 是否需要被刪除掉. int firstSpace = line.indexOf(' '); if (firstSpace == -1) { throw new IOException("unexpected journal line: " + line); } int keyBegin = firstSpace + 1; int secondSpace = line.indexOf(' ', keyBegin); // 查找第二個(gè)空格. final String key; if (secondSpace == -1) { key = line.substring(keyBegin); //判斷狀態(tài)是否被標(biāo)志位 REMOVE. 是的話, 將會(huì)被移除. if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) { //lruEntries 是沒有數(shù)據(jù)的. 刪除對(duì)應(yīng)的標(biāo)識(shí), 也就是 一個(gè) key(一個(gè)文件) 的可能被多次操作. lruEntries.remove(key); return; } } else { key = line.substring(keyBegin, secondSpace); } //將key 值和 Entry() 保存在 lruEntries 中. 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)) { //當(dāng)前的狀態(tài)為 Clean , 即將數(shù)據(jù)保存起來了, 或者洗衣個(gè)將會(huì)刪除數(shù)據(jù). //獲取key 之后的string, 使用空格分割, 分割出來的 lengths 即時(shí)對(duì)應(yīng)的 entry lengths 的值. String[] parts = line.substring(secondSpace + 1).split(" "); entry.readable = true; entry.currentEditor = null; entry.setLengths(parts); //設(shè)置文件的長(zhǎng)度. } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { entry.currentEditor = new Editor(entry); //如果文件是臟數(shù)據(jù)(正在操作)時(shí). 分配一個(gè)新的 Editor(關(guān)于 entry的 Editer(文件流)) } 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. * * 1. 計(jì)算已經(jīng)保存文件的長(zhǎng)度,(CLEAN 標(biāo)記的) * 2. 刪除 DIRTY 標(biāo)記的條目對(duì)應(yīng)的 文本文件和 設(shè)置 entry 為null. * 并將自己從原本的LUR數(shù)列中刪除掉, 擦除記錄. */ private void processJournal() throws IOException { deleteIfExists(journalFileTmp);//刪除 臨時(shí)文件. for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) { Entry entry = i.next(); //文件的操作點(diǎn)是 CLEAR. 也就是干凈的, if (entry.currentEditor == null) { //valueCount 是針對(duì)一個(gè) key 能存多上個(gè) value., 數(shù)據(jù)存在 Entry 里面. for (int t = 0; t < valueCount; t++) { //增加文件的長(zhǎng)度, size. size += entry.lengths[t]; } } else { entry.currentEditor = null; //刪除對(duì)應(yīng)的 cleanFIle 和臟數(shù)據(jù), 只要key 值被標(biāo)記了 REMOVE / 臟數(shù)據(jù)操作的標(biāo)記, 那么之前就會(huì)有 CLEAN 操作 // 這個(gè)地方會(huì)將原本的操作也刪除掉. for (int t = 0; t < valueCount; t++) { deleteIfExists(entry.getCleanFile(t)); deleteIfExists(entry.getDirtyFile(t)); } //從數(shù)組中刪除自己. i.remove(); } } }
-
open 總結(jié)
經(jīng)過open以后氮采,journal文件肯定存在了殷绍;lruEntries里面肯定有值了;size存儲(chǔ)了當(dāng)前所有的實(shí)體占據(jù)的容量鹊漠;主到。
存入緩存
-
示例
String key = generateKey(url); DiskLruCache.Editor editor = mDiskLruCache.edit(key); OuputStream os = editor.newOutputStream(0); //...after op editor.commit()殖侵;
-
調(diào)用對(duì)外部提供的 edit 函數(shù), 獲取 Editor 對(duì)象
/** * Returns an editor for the entry named {@code key}, or null if another * edit is in progress. * * 對(duì)外開發(fā) 獲取 Editor 對(duì)象的函數(shù), 根據(jù) KEY獲取 Editor 對(duì)象 */ public Editor edit(String key) throws IOException { return edit(key, ANY_SEQUENCE_NUMBER); } /** * 創(chuàng)建一個(gè)新的 Editor 對(duì)象, 將從 LUR 里面獲取的 Entry / 重新創(chuàng)建的 Entry 對(duì)象賦值到 Editor 上面去. * 并給 Entry 賦值 entry.currentEditor = editor. * * 在日志文件中寫入 DIRTY 操作日志. * * 注: 在初始化 Editor 之前,會(huì)先判斷 entry.currentEditor != null , * 如果 * * 1. 檢查當(dāng)前的 write 是否被close, 檢查key值的正常 * 2. 創(chuàng)建一個(gè)新的 KEY / LUR 中獲取, 并將 editor.currentEntry 指向當(dāng)前的 entry. * 3. 記錄一行臟數(shù)據(jù)操作, 并返回 Editor對(duì)象, */ private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { checkNotClosed(); validateKey(key); Entry entry = lruEntries.get(key); if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) { return null; // Snapshot is stale. } //當(dāng) entry 為 null 時(shí), 創(chuàng)建一個(gè) entry 對(duì)象并保存到 LUR 里面去. if (entry == null) { entry = new Entry(key); lruEntries.put(key, entry); //當(dāng)前的 editor 正在被操作, 直接返回 null. } else if (entry.currentEditor != null) { return null; // Another edit is in progress. } //創(chuàng)建一個(gè)新的 Editor對(duì)象, 并設(shè)置 currentEditor 對(duì)象為之前的 Entry, 或者 lruEntries.get(key) 獲取的editor Editor editor = new Editor(entry); entry.currentEditor = editor; //設(shè)置臟當(dāng)前的KEY 為臟數(shù)據(jù)標(biāo)記. // Flush the journal before creating files to prevent file leaks. journalWriter.write(DIRTY + ' ' + key + '\n'); journalWriter.flush(); return editor; }
-
Editor/ Entry 對(duì)象
//空的 output , 對(duì)write 做的操作都不做任何事情. private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { @Override public void write(int b) throws IOException { // Eat all writes silently. Nom nom. } }; /** Edits the values for an entry. */ public final class Editor { private final Entry entry; //對(duì)應(yīng)的 寫入者. private final boolean[] written; //FilterOutputStream 的任何流操作,出了異常,都會(huì)被標(biāo)記為 error true. private boolean hasErrors; //是否提交完成標(biāo)記 private boolean committed; private Editor(Entry entry) { this.entry = entry; this.written = (entry.readable) ? null : new boolean[valueCount]; } /** * Returns an unbuffered input stream to read the last committed value, * or null if no value has been committed. * inputStream 需要外部自己close. */ public InputStream newInputStream(int index) throws IOException { synchronized (DiskLruCache.this) { if (entry.currentEditor != this) { throw new IllegalStateException(); } //數(shù)據(jù)沒有被寫入過. if (!entry.readable) { return null; } //數(shù)據(jù)被寫入過, 直接返回 文件流 對(duì)象. try { return new FileInputStream(entry.getCleanFile(index)); } catch (FileNotFoundException e) { return null; } } } /** * Returns the last committed value as a string, or null if no value * has been committed. */ public String getString(int index) throws IOException { InputStream in = newInputStream(index); return in != null ? inputStreamToString(in) : null; } /** * Returns a new unbuffered output stream to write the value at * {@code index}. If the underlying output stream encounters errors * when writing to the filesystem, this edit will be aborted when * {@link #commit} is called. The returned output stream does not throw * IOExceptions. */ /** * index 指的是在用戶傳入的 一個(gè)key 對(duì)應(yīng)幾個(gè) 文件的下標(biāo), */ 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(); } //當(dāng)對(duì)應(yīng)的 entry 對(duì)應(yīng)的 KEY 之前沒有被寫入, 那么 WRITTEN[i] 會(huì)被置為 true. if (!entry.readable) { written[index] = true; } //獲取 outPut 寫入文件的存放位置在 臟文件中,(.tmp), 其中是根據(jù) index 來命名的. File dirtyFile = entry.getDirtyFile(index); FileOutputStream outputStream; //兩次打開文件, 第一次可以判斷文件夾不存在的時(shí)候, 會(huì)創(chuàng)建文件夾, //然后, 再次打開文件, //如果還是報(bào)錯(cuò)誤了, 會(huì)返回一個(gè)對(duì) Write 無處理的的 output, 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; } } //返回正常情況下的 OutPutStream., //數(shù)據(jù)操作中的 OutputWriter 操作的數(shù)據(jù)會(huì)被直接寫入到臟文件中保存. return new FaultHidingOutputStream(outputStream); } } /** Sets the value at {@code index} to {@code value}. */ public void set(int index, String value) throws IOException { //單獨(dú)創(chuàng)建一個(gè) Writer 對(duì)像, 將數(shù)據(jù)保存到指定文件中. Writer writer = null; try { writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8); 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. * * 提交數(shù)據(jù)到硬盤上,當(dāng) hasErrors 不為 true 時(shí), * 會(huì)調(diào)用 completeEdit(this, true) 函數(shù)將 數(shù)據(jù)寫入到指定位置, * * 將tmp 文件寫成 clean 文件. */ public void commit() throws IOException { if (hasErrors) { completeEdit(this, false); remove(entry.key); // The previous entry is stale. } else { completeEdit(this, true); } committed = true; } /** * Aborts this edit. This releases the edit lock so another edit may be * started on the same key. * * 將temp 文件刪除掉. */ public void abort() throws IOException { completeEdit(this, false); } //在提交中, 想要中斷提交. public void abortUnlessCommitted() { if (!committed) { try { abort(); } catch (IOException ignored) { } } } //輸出流的寫函數(shù)都被 try/catch, 一旦報(bào)錯(cuò),就在commit 的時(shí)候, 將 DIRTY 文件刪除掉 //并將對(duì)應(yīng)的 KEY 從對(duì)應(yīng)的 LUR 數(shù)組中移除. private class FaultHidingOutputStream extends FilterOutputStream { private FaultHidingOutputStream(OutputStream out) { super(out); } @Override public void write(int oneByte) { try { out.write(oneByte); } catch (IOException e) { hasErrors = true; } } @Override public void write(byte[] buffer, int offset, int length) { try { out.write(buffer, offset, length); } catch (IOException e) { hasErrors = true; } } @Override public void close() { try { out.close(); } catch (IOException e) { hasErrors = true; } } @Override public void flush() { try { out.flush(); } catch (IOException e) { hasErrors = true; } } } } private final class Entry { //entry 對(duì)應(yīng)的KEY值, 唯一標(biāo)識(shí)符 private final String key; /** Lengths of this entry's files. * 一個(gè) KEY 對(duì)應(yīng)的文件個(gè)數(shù), 使用lengths 來表示. * */ private final long[] lengths; /** True if this entry has ever been published. * 設(shè)置當(dāng)前文件是否 可讀, 在被成功寫入時(shí) "CLEAR" 會(huì)伴隨這對(duì)應(yīng)的 entry.readable 被標(biāo)記為true. * readJournalLine() 時(shí), 當(dāng)前的標(biāo)志為 CLEAN時(shí), 會(huì)被標(biāo)記為可讀. * */ private boolean readable; /** The ongoing edit or null if this entry is not being edited. * 當(dāng)前編輯, 當(dāng)狀態(tài)為 Clear 是, 這個(gè)對(duì)象為 null, * 當(dāng)為臟數(shù)據(jù)時(shí), currentEditor 不為空. * */ private Editor currentEditor; /** The sequence number of the most recently committed edit to this entry. */ //最近提交的序列號(hào), 主要的用處是 Snapshot 對(duì)象獲取快照, 但是原本的文件被改動(dòng)了, 就直接return null. private long sequenceNumber; //輸入一個(gè)新 KEY, 并創(chuàng)建一個(gè) 長(zhǎng)度為 valueCount 的 int 數(shù)組 lengths private Entry(String key) { this.key = key; this.lengths = new long[valueCount]; } //根據(jù) lengths 的個(gè)數(shù),返回 String, 使用分隔符 ' ' 分割. 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". * 這里的 valueCount 是指的起初 open() 設(shè)置進(jìn)來的一個(gè) Key 對(duì)應(yīng)幾個(gè)文件, * 其中每個(gè)文件以 0,1,2,3,... 來區(qū)分. * * 將對(duì)應(yīng)文件的長(zhǎng)度設(shè)置進(jìn)來. * */ private void setLengths(String[] strings) throws IOException { if (strings.length != valueCount) { throw invalidLengths(strings); } //保存 lengths 到 Entry 的 length 上面, 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)); } //對(duì)應(yīng)的文件使用 test.0, test.1 ... 來存放 public File getCleanFile(int i) { return new File(directory, key + "." + i); } //對(duì)應(yīng)的文件使用 test.0, test.1 ... 來存放 public File getDirtyFile(int i) { return new File(directory, key + "." + i + ".tmp"); } }
-
對(duì) outPutSrream 操作的關(guān)鍵操作, 調(diào)用 edit的commit 函數(shù)才會(huì)被調(diào)用, 在editor 參數(shù)被調(diào)用的 時(shí)候, 返回的 OutputStream 指向的是 DIRTY 文件, 不存在,創(chuàng)建.
/** * 1. 保存/刪除 緩存文件 --> success. success = true 時(shí), 將臟數(shù)據(jù)文件 寫成對(duì)應(yīng)的 clean 文件, 并刪除原本的臟數(shù)據(jù)文件 * success = false時(shí), 將臟數(shù)據(jù)直接刪除. 不保存成 clean文件, * 2. 多余的操作++ (DIRTY). * * 3. 檢查是否關(guān)于 valueCount 長(zhǎng)度的 output 都同事被操作聊(第一次的時(shí)候) , 不然直接報(bào)錯(cuò)了. readable(只有已經(jīng)被寫入的文件才會(huì)有這個(gè)標(biāo)記,) * * 4. * */ 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. //第一次寫入文件,必須每個(gè)對(duì)應(yīng)的index 都有值, 不然這個(gè)地方會(huì) 報(bào)錯(cuò), // 當(dāng) valueCount = 1 的時(shí)候, 這個(gè)比較好理解, //當(dāng) valueCount = 3 的時(shí)候, 每次提交新的 KEY 的時(shí)候, 對(duì)應(yīng)的 index 必須在 commit() 函數(shù)之前先調(diào)用, //不然在這個(gè)位置會(huì)報(bào)錯(cuò)誤, if (success && !entry.readable) { for (int i = 0; i < valueCount; i++) { //written 這個(gè)數(shù)組是在 readable 為 false 的時(shí)候才會(huì)被置為 true. //也就是第一次使用對(duì)應(yīng)KEY 時(shí)才會(huì)被置為TRUE. if (!editor.written[i]) { editor.abort(); //新創(chuàng)建的條目沒有為索引創(chuàng)建價(jià)值 throw new IllegalStateException("Newly created entry didn't create value for index " + i); } //獲取使用 newOutPutStream 操作的那個(gè)臟文件是否存在, //不存在時(shí), 直接 return , 并記那個(gè) success 置為false. if (!entry.getDirtyFile(i).exists()) { //刪除對(duì)應(yīng)的臟文件, 并重新寫入對(duì)應(yīng)的 CLEAN / REMOVE 的記錄. editor.abort(); return; } } } //將對(duì)應(yīng)的臟文件修改為CLEAN 文件. for (int i = 0; i < valueCount; i++) { File dirty = entry.getDirtyFile(i); if (success) { //對(duì)應(yīng)的 index 文件存在的時(shí)候,才會(huì)將文件 rename. if (dirty.exists()) { File clean = entry.getCleanFile(i); dirty.renameTo(clean); //重新設(shè)置 size 大小, 相對(duì)臟文件. long oldLength = entry.lengths[i]; long newLength = clean.length(); entry.lengths[i] = newLength; size = size - oldLength + newLength; } } else { //success = false 時(shí), 將刪除掉 臟數(shù)據(jù)文件. deleteIfExists(dirty); } } redundantOpCount++; entry.currentEditor = null; //當(dāng) readable 為true (之前被寫入過), 或者 success時(shí), 將重新寫入 SIZE. if (entry.readable | success) { entry.readable = true; //重新寫入 文件lengths 長(zhǎng)度, 當(dāng)對(duì)應(yīng)的文件沒有數(shù)據(jù)時(shí), 長(zhǎng)度為0. journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); //當(dāng)重新寫入了 CLEAN 時(shí), entry 會(huì)被重新寫入 sequenceNumber. if (success) { entry.sequenceNumber = nextSequenceNumber++; } } else { //刪除 LUR 里面的 KEY , 并寫入 REMOVE 操作. lruEntries.remove(entry.key); journalWriter.write(REMOVE + ' ' + entry.key + '\n'); } //關(guān)閉寫入操作 journalWriter.flush(); //重新計(jì)算大小. if (size > maxSize || journalRebuildRequired()) { executorService.submit(cleanupCallable); } }
-
保證文件大小限制大小操作
//執(zhí)行的時(shí)機(jī): //1. 在使用 get() 函數(shù)獲取 Snapshot 對(duì)象的時(shí)候, 可能會(huì)觸發(fā)一次 if (journalRebuildRequired()) //2. 重新設(shè)置 MaxSize setMaxSize(maxSize) if( journalRebuildRequired() ) //3. 調(diào)用數(shù)據(jù)提交時(shí): completeEdit(); if (size > maxSize || journalRebuildRequired()) //4. 調(diào)用 remove(key) 函數(shù)時(shí). if( journalRebuildRequired() ) // journalRebuildRequired() 函數(shù), 判斷的是 : // redundantOpCount 參數(shù): 多余的操作次數(shù): 1. 在調(diào)用 readJournal() 的時(shí)候會(huì)只有 clean() 并沒有被移除的條目會(huì)被添加到 LUR 中, // 然后其他的多余的操作還有 remove() 會(huì)寫入 REMOVE 字段, // completeEdit() 的時(shí)候會(huì)寫入 DIRTY 字段, // get() 的時(shí)候會(huì)顯示 READ 參數(shù). private final Callable<Void> cleanupCallable = new Callable<Void>() { public Void call() throws Exception { synchronized (DiskLruCache.this) { if (journalWriter == null) { return null; // Closed. } //當(dāng) 當(dāng)前文件 SIZE 大于設(shè)置進(jìn)來的 maxSize 值, 會(huì)移除不常使用的文件, //保證保存的文件是 最常使用的, 并全部長(zhǎng)度加起來 < maxSize. //LUR 算法的優(yōu)勢(shì). trimToSize(); if (journalRebuildRequired()) { rebuildJournal(); redundantOpCount = 0; } } return null; } };
-
檢測(cè)文件大小操作
//只有當(dāng) 多余操作大于 2000 并且多余操作大于等于 緩存數(shù)據(jù)的長(zhǎng)度. private boolean journalRebuildRequired() { final int redundantOpCompactThreshold = 2000; return redundantOpCount >= redundantOpCompactThreshold // && redundantOpCount >= lruEntries.size(); }
-
檢查 SIZE 是否大于 maxSize, 大于的時(shí)候,會(huì)刪除不常用的 文件, 調(diào)用 remove 函數(shù), 刪除 KEY對(duì)應(yīng)的文件.
調(diào)用時(shí)機(jī):
- cleanupCallable() 被調(diào)用
- close() 被調(diào)用時(shí)
- flash() 被調(diào)用時(shí)
/** * 借助 LUR 算法的幫助, 函數(shù) trimToSize() 會(huì)刪除最近不常使用的 key. * http://blog.csdn.net/justloveyou_/article/details/71713781 */ private void trimToSize() throws IOException { while (size > maxSize) { Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next(); remove(toEvict.getKey()); } }
讀操作
-
示例
try { String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg"; String key = hashKeyForDisk(imageUrl); DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) { InputStream is = snapShot.getInputStream(0); Bitmap bitmap = BitmapFactory.decodeStream(is); mImage.setImageBitmap(bitmap); } } catch (IOException e) { e.printStackTrace(); }
get() 函數(shù)
/**
* 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.
*
* 1. 通過 KEY 拿到 readable 的 entry 對(duì)象, 可以被讀寫的 CLEAN 標(biāo)記的 文件
* 2. 申請(qǐng)一個(gè) 長(zhǎng)度為 valueCount 的 InputStream 數(shù)據(jù), 并將對(duì)應(yīng) entry 的 clean 文件賦值給 inputStream
* 3. 為 KEY 寫入 READ 標(biāo)記, 多余的操作++
* 4. 返回一個(gè) Snapshot對(duì)象.
*/
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!
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 Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
}
其他操作
-
remove(key)
/** * 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. * * 1. 判斷當(dāng)前entry 的 currentEditor 是否為null, 為null 證明沒有在操作, * 在被操作, 直接返回 false. * 2. 根據(jù) valueCount 長(zhǎng)度, 循環(huán)減去 將要被刪除的文件長(zhǎng)度大小, 更新 size 大小, * 重置 entry lengths 全部為0. * * 3. 多余的操作++ , 從 LUR 中刪除 key, 和 檢查當(dāng)前多余操作是否過多, * 如果過多會(huì)被指向重寫生成 journal文件,刪除文件中 REMORE, READ 操作, 和已經(jīng)被刪除文件的 * KEY 操作 */ public synchronized boolean remove(String key) throws IOException { checkNotClosed(); //判斷 寫入流 是否為空, validateKey(key); Entry entry = lruEntries.get(key); //當(dāng)當(dāng)前文件還在被編輯, 或者當(dāng)前的 entry 不被保存在 LUR 里面, 會(huì)直接返回 false. if (entry == null || entry.currentEditor != null) { return false; } //刪除 對(duì)應(yīng)的cleanFile 文件, 一個(gè) key 對(duì)應(yīng)對(duì)個(gè)文件. for (int i = 0; i < valueCount; i++) { File file = entry.getCleanFile(i); if (file.exists() && !file.delete()) { throw new IOException("failed to delete " + file); } //實(shí)時(shí)改變 size 大小 size -= entry.lengths[i]; //修改entry 的長(zhǎng)度 length 大小 entry.lengths[i] = 0; } redundantOpCount++; //寫入 remove 操作條例到文件中. journalWriter.append(REMOVE + ' ' + key + '\n'); //刪除key. lruEntries.remove(key); //檢測(cè)多余的操作數(shù) > 2000 && > lur.size(). if (journalRebuildRequired()) { //重新 rebuilder. executorService.submit(cleanupCallable); } return true; }
public File getDirectory() : 返回當(dāng)前緩存數(shù)據(jù)的目錄
public synchronized long getMaxSize() : 獲取設(shè)置的緩存的最大大小
public synchronized long size() : //獲取當(dāng)前存儲(chǔ)的 占用 硬盤空間, byte.
public synchronized boolean isClosed() : 判斷寫 日志文件的 journalWriter是否被 close() 掉了, 置為null
public synchronized void flush() : 調(diào)用 trimToSize() 函數(shù), 和關(guān)閉 調(diào)用 journalWriter 的 flush() 函數(shù)
public synchronized void close() : 關(guān)閉當(dāng)前寫 LOG 文件的 journalWriter , 并停止所有的 寫操作, 中斷
public void delete() : 刪除傳入的 緩存目錄, 遞歸刪除全部的文件 .
Done.