DiskLruCache.java 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927
  1. package com.yc.videosqllite.disk;
  2. import java.io.BufferedWriter;
  3. import java.io.Closeable;
  4. import java.io.EOFException;
  5. import java.io.File;
  6. import java.io.FileInputStream;
  7. import java.io.FileNotFoundException;
  8. import java.io.FileOutputStream;
  9. import java.io.IOException;
  10. import java.io.InputStream;
  11. import java.io.InputStreamReader;
  12. import java.io.OutputStream;
  13. import java.io.OutputStreamWriter;
  14. import java.io.Writer;
  15. import java.util.ArrayList;
  16. import java.util.Iterator;
  17. import java.util.LinkedHashMap;
  18. import java.util.Map;
  19. import java.util.concurrent.Callable;
  20. import java.util.concurrent.LinkedBlockingQueue;
  21. import java.util.concurrent.ThreadFactory;
  22. import java.util.concurrent.ThreadPoolExecutor;
  23. import java.util.concurrent.TimeUnit;
  24. /**
  25. * <pre>
  26. * @author yangchong
  27. * email : yangchong211@163.com
  28. * time : 2020/8/6
  29. * desc : 磁盘缓存类
  30. * revise:
  31. * </pre>
  32. */
  33. public final class DiskLruCache implements Closeable {
  34. /**
  35. * libcore.io.DiskLruCache
  36. * 1
  37. * 1
  38. * 1
  39. *
  40. * DIRTY 27c7e00adbacc71dc793e5e7bf02f861
  41. * CLEAN 27c7e00adbacc71dc793e5e7bf02f861 1208
  42. * READ 27c7e00adbacc71dc793e5e7bf02f861
  43. * DIRTY b80f9eec4b616dc6682c7fa8bas2061f
  44. * CLEAN b80f9eec4b616dc6682c7fa8bas2061f 1208
  45. * READ b80f9eec4b616dc6682c7fa8bas2061f
  46. * DIRTY be3fgac81c12a08e89088555d85dfd2b
  47. * CLEAN be3fgac81c12a08e89088555d85dfd2b 99
  48. * READ be3fgac81c12a08e89088555d85dfd2b
  49. * DIRTY 536990f4dbddfghcfbb8f350a941wsxd
  50. * REMOVE 536990f4dbddfghcfbb8f350a941wsxd
  51. *
  52. * 第1行:libcore.io.DiskLruCache 是固定字符串,表明使用的是 DiskLruCache 技术;
  53. * 第2行:DiskLruCache 的版本号,源码中为常量 1;
  54. * 第3行:APP 的版本号,即我们在 open() 方法里传入的版本号;
  55. * 第4行:valueCount,这个值也是在 open() 方法中传入的,指每个 key 对应几个文件,通常情况下都为 1;
  56. * 第5行:空行
  57. *
  58. *
  59. */
  60. static final String JOURNAL_FILE = "journal";
  61. static final String JOURNAL_FILE_TEMP = "journal.tmp";
  62. static final String JOURNAL_FILE_BACKUP = "journal.bkp";
  63. static final String MAGIC = "libcore.io.DiskLruCache";
  64. static final String VERSION_1 = "1";
  65. static final long ANY_SEQUENCE_NUMBER = -1;
  66. private static final String CLEAN = "CLEAN";
  67. private static final String DIRTY = "DIRTY";
  68. private static final String REMOVE = "REMOVE";
  69. private static final String READ = "READ";
  70. private final File directory;
  71. private final File journalFile;
  72. private final File journalFileTmp;
  73. private final File journalFileBackup;
  74. private final int appVersion;
  75. private long maxSize;
  76. private final int valueCount;
  77. private long size = 0;
  78. private Writer journalWriter;
  79. private final LinkedHashMap<String, Entry> lruEntries =
  80. new LinkedHashMap<String, Entry>(0, 0.75f, true);
  81. private int redundantOpCount;
  82. /**
  83. * To differentiate between old and current snapshots, each entry is given
  84. * a sequence number each time an edit is committed. A snapshot is stale if
  85. * its sequence number is not equal to its entry's sequence number.
  86. */
  87. private long nextSequenceNumber = 0;
  88. /**
  89. * This cache uses a single background thread to evict entries.
  90. */
  91. final ThreadPoolExecutor executorService =
  92. new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
  93. new DiskLruCacheThreadFactory());
  94. private final Callable<Void> cleanupCallable = new Callable<Void>() {
  95. public Void call() throws Exception {
  96. synchronized (DiskLruCache.this) {
  97. if (journalWriter == null) {
  98. return null; // Closed.
  99. }
  100. trimToSize();
  101. if (journalRebuildRequired()) {
  102. //创建一个新的日志,删除多余的信息
  103. rebuildJournal();
  104. redundantOpCount = 0;
  105. }
  106. }
  107. return null;
  108. }
  109. };
  110. private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
  111. this.directory = directory;
  112. this.appVersion = appVersion;
  113. this.journalFile = new File(directory, JOURNAL_FILE);
  114. this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
  115. this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
  116. this.valueCount = valueCount;
  117. this.maxSize = maxSize;
  118. }
  119. /**
  120. * Opens the cache in {@code directory}, creating a cache if none exists
  121. * there.
  122. * 第一个参数表示磁盘缓存在文件系统中的存储路径。
  123. * 第二个参数表示应用的版本号,一般设为 1 即可。当版本号发生改变时 DiskLruCache 会清空之前所有的缓存文件,
  124. * 而这个特性在实际开发中作用并不大,很多情况下即使应用的版本号发生了改变缓存文件却仍然是有效的,因此这个参数设为 1 比较好。
  125. * 第三个参数表示同一个 key 可以对应多少个缓存文件,一般设为 1 即可。
  126. * 第四个参数表示缓存的总大小,比如 50MB,当缓存大小超出这个设定值后,DiskLruCache 会清除一些缓存从而保证总大小不大于这个设定值。
  127. * @param directory a writable directory
  128. * @param valueCount the number of values per cache entry. Must be positive.
  129. * @param maxSize the maximum number of bytes this cache should use to store
  130. * @throws IOException if reading or writing the cache directory fails
  131. */
  132. public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
  133. throws IOException {
  134. if (maxSize <= 0) {
  135. throw new IllegalArgumentException("maxSize <= 0");
  136. }
  137. if (valueCount <= 0) {
  138. throw new IllegalArgumentException("valueCount <= 0");
  139. }
  140. // 如果存在备份日志文件,则使用它
  141. File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
  142. if (backupFile.exists()) {
  143. File journalFile = new File(directory, JOURNAL_FILE);
  144. // 如果存在正式的日志文件,则将备份日志文件删除
  145. if (journalFile.exists()) {
  146. backupFile.delete();
  147. } else {
  148. renameTo(backupFile, journalFile, false);
  149. }
  150. }
  151. // 首先尝试读取日志文件
  152. DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
  153. if (cache.journalFile.exists()) {
  154. try {
  155. //日志文件的读取过程
  156. cache.readJournal();
  157. //用于统计缓存文件的总体大小,并删除脏文件
  158. cache.processJournal();
  159. return cache;
  160. } catch (IOException journalIsCorrupt) {
  161. System.out
  162. .println("DiskLruCache "
  163. + directory
  164. + " is corrupt: "
  165. + journalIsCorrupt.getMessage()
  166. + ", removing");
  167. cache.delete();
  168. }
  169. }
  170. // 此时日志文件不存在或读取出错,新建一个DiskLruCache实例
  171. directory.mkdirs();
  172. cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
  173. //创建一个新的日志,删除多余的信息
  174. cache.rebuildJournal();
  175. return cache;
  176. }
  177. /**
  178. * 日志文件的读取过程
  179. * readJournal()方法其实就是通过readJournalLine(reader.readLine())方法读取日志文件中的每一行,
  180. * 最终会读取到lruEntries中,lruEntries是DiskLruCache在内存中的表现形式。
  181. * @throws IOException 异常
  182. */
  183. private void readJournal() throws IOException {
  184. StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), DiskUtils.US_ASCII);
  185. try {
  186. String magic = reader.readLine();
  187. String version = reader.readLine();
  188. String appVersionString = reader.readLine();
  189. String valueCountString = reader.readLine();
  190. String blank = reader.readLine();
  191. if (!MAGIC.equals(magic)
  192. || !VERSION_1.equals(version)
  193. || !Integer.toString(appVersion).equals(appVersionString)
  194. || !Integer.toString(valueCount).equals(valueCountString)
  195. || !"".equals(blank)) {
  196. throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
  197. + valueCountString + ", " + blank + "]");
  198. }
  199. int lineCount = 0;
  200. while (true) {
  201. try {
  202. //该方法用于读取每一行日志
  203. readJournalLine(reader.readLine());
  204. lineCount++;
  205. } catch (EOFException endOfJournal) {
  206. break;
  207. }
  208. }
  209. redundantOpCount = lineCount - lruEntries.size();
  210. // If we ended on a truncated line, rebuild the journal before appending to it.
  211. if (reader.hasUnterminatedLine()) {
  212. //创建一个新的日志,删除多余的信息
  213. rebuildJournal();
  214. } else {
  215. journalWriter = new BufferedWriter(new OutputStreamWriter(
  216. new FileOutputStream(journalFile, true), DiskUtils.US_ASCII));
  217. }
  218. } finally {
  219. DiskUtils.closeQuietly(reader);
  220. }
  221. }
  222. /**
  223. * 该方法用于读取每一行日志。日志文件的每一行都是DIRTY、CLEAN、READ或REMOVE四种行为之一,那么该方法就需要对这4中情况分别处理。
  224. * 大概思路:
  225. * 1.首先取出该行记录的key,然后根据该记录是否为REMOVE进行不同的操作,如果是REMOVE,则将该key的缓存从lruEntries中移除。
  226. * 2.如果不是REMOVE,说明该key存在一个对应的缓存实体Entry,则先新建一个Entry并添加到lruEntries中。
  227. * 3.之后再判断日志的类型,如果日志是CLEAN,代表该文件已经保存完毕了,将currentEditor设置为null;
  228. * 4.如果日志是DIRTY,代表文件没有保存完毕,为其currentEditor新建一个Editor。
  229. *
  230. * 为什么要这么做呢?
  231. * 保存一个文件时会先写入DIRTY日志,保存成功后再写入CLEAN日志,一般来说这两条日志会成对出现。
  232. * 这里的currentEditor相当于一个标志位,如果为空,表示文件完整,如果不为空,表示该文件是临时文件。
  233. * @param line 每行内容
  234. * @throws IOException io流异常
  235. */
  236. private void readJournalLine(String line) throws IOException {
  237. int firstSpace = line.indexOf(' ');
  238. if (firstSpace == -1) {
  239. throw new IOException("unexpected journal line: " + line);
  240. }
  241. int keyBegin = firstSpace + 1;
  242. int secondSpace = line.indexOf(' ', keyBegin);
  243. final String key;
  244. if (secondSpace == -1) {
  245. key = line.substring(keyBegin);
  246. // 如果是REMOVE,则将该key代表的缓存从lruEntries中移除
  247. if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
  248. lruEntries.remove(key);
  249. return;
  250. }
  251. } else {
  252. key = line.substring(keyBegin, secondSpace);
  253. }
  254. //取出当前key对应的缓存Entry
  255. Entry entry = lruEntries.get(key);
  256. if (entry == null) {
  257. entry = new Entry(key);
  258. lruEntries.put(key, entry);
  259. }
  260. // 如果是CLEAN、DIRTY或READ
  261. if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
  262. String[] parts = line.substring(secondSpace + 1).split(" ");
  263. entry.readable = true;
  264. entry.currentEditor = null;
  265. entry.setLengths(parts);
  266. } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
  267. entry.currentEditor = new Editor(entry);
  268. } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
  269. // This work was already done by calling lruEntries.get().
  270. } else {
  271. throw new IOException("unexpected journal line: " + line);
  272. }
  273. }
  274. /**
  275. * 计算初始大小并收集垃圾,作为打开缓存。假设脏条目是不一致的,将被删除。
  276. *
  277. */
  278. private void processJournal() throws IOException {
  279. deleteIfExists(journalFileTmp);
  280. for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
  281. Entry entry = i.next();
  282. if (entry.currentEditor == null) {
  283. for (int t = 0; t < valueCount; t++) {
  284. size += entry.lengths[t];
  285. }
  286. } else {
  287. entry.currentEditor = null;
  288. for (int t = 0; t < valueCount; t++) {
  289. deleteIfExists(entry.getCleanFile(t));
  290. deleteIfExists(entry.getDirtyFile(t));
  291. }
  292. i.remove();
  293. }
  294. }
  295. }
  296. /**
  297. * 创建一个新的日志,删除多余的信息。如果当前日志存在,则替换当前日志。
  298. */
  299. private synchronized void rebuildJournal() throws IOException {
  300. if (journalWriter != null) {
  301. journalWriter.close();
  302. }
  303. Writer writer = new BufferedWriter(
  304. new OutputStreamWriter(new FileOutputStream(journalFileTmp), DiskUtils.US_ASCII));
  305. try {
  306. writer.write(MAGIC);
  307. writer.write("\n");
  308. writer.write(VERSION_1);
  309. writer.write("\n");
  310. writer.write(Integer.toString(appVersion));
  311. writer.write("\n");
  312. writer.write(Integer.toString(valueCount));
  313. writer.write("\n");
  314. writer.write("\n");
  315. for (Entry entry : lruEntries.values()) {
  316. if (entry.currentEditor != null) {
  317. writer.write(DIRTY + ' ' + entry.key + '\n');
  318. } else {
  319. writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
  320. }
  321. }
  322. } finally {
  323. writer.close();
  324. }
  325. if (journalFile.exists()) {
  326. renameTo(journalFile, journalFileBackup, true);
  327. }
  328. renameTo(journalFileTmp, journalFile, false);
  329. journalFileBackup.delete();
  330. journalWriter = new BufferedWriter(
  331. new OutputStreamWriter(new FileOutputStream(journalFile, true), DiskUtils.US_ASCII));
  332. }
  333. private static void deleteIfExists(File file) throws IOException {
  334. if (file.exists() && !file.delete()) {
  335. throw new IOException();
  336. }
  337. }
  338. private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
  339. if (deleteDestination) {
  340. deleteIfExists(to);
  341. }
  342. if (!from.renameTo(to)) {
  343. throw new IOException();
  344. }
  345. }
  346. /**
  347. * 读取缓存
  348. * 返回名为{@code key}的条目的快照,如果不存在则返回null,这是当前不可读的。如果一个值被返回,它将被移动到LRU队列的头。
  349. */
  350. public synchronized Value get(String key) throws IOException {
  351. checkNotClosed();
  352. //取出当前key对应的缓存Entry
  353. Entry entry = lruEntries.get(key);
  354. if (entry == null) {
  355. return null;
  356. }
  357. if (!entry.readable) {
  358. return null;
  359. }
  360. for (File file : entry.cleanFiles) {
  361. // A file must have been deleted manually!
  362. if (!file.exists()) {
  363. // 除非用户手动删了文件, 否则不会执行到这里...
  364. return null;
  365. }
  366. }
  367. redundantOpCount++;
  368. journalWriter.append(READ);
  369. journalWriter.append(' ');
  370. journalWriter.append(key);
  371. journalWriter.append('\n');
  372. if (journalRebuildRequired()) {
  373. executorService.submit(cleanupCallable);
  374. }
  375. return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
  376. }
  377. /**
  378. * 返回名为{@code key}的条目的编辑器,如果正在进行另一个编辑,则返回null。
  379. * 这个key将会成为缓存文件的文件名
  380. */
  381. public Editor edit(String key) throws IOException {
  382. return edit(key, ANY_SEQUENCE_NUMBER);
  383. }
  384. /**
  385. * 如何写缓存?
  386. * 写缓存的时候需要先通过edit(String key)方法新建一个Editor,然后将数据写入Editor的输出流中,最后成功则调用
  387. * Editor.commit(),失败则调用Editor.abort()。
  388. *
  389. * 该方法大概思路
  390. * 1.取出当前key对应的缓存Entry,如果Entry不存在则新建并添加到lruEntries中,
  391. * 2.如果存在且entry.currentEditor不为空,表示Entry正在进行缓存编辑。
  392. * 3.随后新建一个Editor,并在日志文件中输出一行DIRTY日志表示开始编辑缓存文件。
  393. * @param key key
  394. * @param expectedSequenceNumber number
  395. * @return
  396. * @throws IOException 异常
  397. */
  398. private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
  399. checkNotClosed();
  400. //取出当前key对应的缓存Entry
  401. Entry entry = lruEntries.get(key);
  402. if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
  403. || entry.sequenceNumber != expectedSequenceNumber)) {
  404. return null; // Value is stale.
  405. }
  406. if (entry == null) {
  407. //如果Entry不存在则新建并添加到lruEntries中
  408. entry = new Entry(key);
  409. lruEntries.put(key, entry);
  410. } else if (entry.currentEditor != null) {
  411. //如果存在且entry.currentEditor不为空,表示Entry正在进行缓存编辑
  412. return null; // Another edit is in progress.
  413. }
  414. //随后新建一个Editor,并在日志文件中输出一行DIRTY日志表示开始编辑缓存文件。
  415. Editor editor = new Editor(entry);
  416. entry.currentEditor = editor;
  417. // Flush the journal before creating files to prevent file leaks.
  418. // 为了防止文件泄露,在创建文件前,将日志立即写入journal中
  419. journalWriter.append(DIRTY);
  420. journalWriter.append(' ');
  421. journalWriter.append(key);
  422. journalWriter.append('\n');
  423. journalWriter.flush();
  424. return editor;
  425. }
  426. /**
  427. * 返回该缓存存储其数据的目录。
  428. */
  429. public File getDirectory() {
  430. return directory;
  431. }
  432. /**
  433. * 返回此缓存用于存储其数据的最大字节数。
  434. */
  435. public synchronized long getMaxSize() {
  436. return maxSize;
  437. }
  438. /**
  439. * 更改缓存可以存储的最大字节数,并在必要时对作业进行排队,以精简现有的存储。
  440. */
  441. public synchronized void setMaxSize(long maxSize) {
  442. this.maxSize = maxSize;
  443. executorService.submit(cleanupCallable);
  444. }
  445. /**
  446. * 返回当前用于在此缓存中存储值的字节数。如果后台删除挂起,这个值可能大于最大大小。
  447. * 这个方法会返回当前缓存路径下所有缓存数据的总字节数,以byte为单位,
  448. * 如果应用程序中需要在界面上显示当前缓存数据的总大小,就可以通过调用这个方法计算出来。
  449. */
  450. public synchronized long size() {
  451. return size;
  452. }
  453. /**
  454. * 该方法首先根据文件写入是否成功来重命名或者删除tmp文件,随后向journal写入日志,最后判断是否需要清理磁盘空间。
  455. * @param editor editor对象
  456. * @param success 是否成功
  457. * @throws IOException
  458. */
  459. private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
  460. Entry entry = editor.entry;
  461. if (entry.currentEditor != editor) {
  462. throw new IllegalStateException();
  463. }
  464. // 如果当前编辑是第一次创建Entry,那么每个索引上都应该有值
  465. // valueCount表示一个Entry中的value数量
  466. if (success && !entry.readable) {
  467. for (int i = 0; i < valueCount; i++) {
  468. if (!editor.written[i]) {
  469. editor.abort();
  470. throw new IllegalStateException("Newly created entry didn't create value for index " + i);
  471. }
  472. if (!entry.getDirtyFile(i).exists()) {
  473. editor.abort();
  474. return;
  475. }
  476. }
  477. }
  478. // 遍历Entry上的每个文件
  479. // 如果编辑成功就将临时文件改名, 如果失败则删除临时文件
  480. for (int i = 0; i < valueCount; i++) {
  481. File dirty = entry.getDirtyFile(i);
  482. if (success) {
  483. if (dirty.exists()) {
  484. File clean = entry.getCleanFile(i);
  485. dirty.renameTo(clean);
  486. long oldLength = entry.lengths[i];
  487. long newLength = clean.length();
  488. entry.lengths[i] = newLength;
  489. size = size - oldLength + newLength;
  490. }
  491. } else {
  492. deleteIfExists(dirty);
  493. }
  494. }
  495. redundantOpCount++;
  496. entry.currentEditor = null;
  497. if (entry.readable | success) {
  498. entry.readable = true;
  499. journalWriter.append(CLEAN);
  500. journalWriter.append(' ');
  501. journalWriter.append(entry.key);
  502. journalWriter.append(entry.getLengths());
  503. journalWriter.append('\n');
  504. if (success) {
  505. // 给Entry的sequenceNumber赋值, 用于标记snapshot是否过期
  506. // 如果Entry和snapshot的sequenceNumber不同, 则表示数据已经过期了
  507. entry.sequenceNumber = nextSequenceNumber++;
  508. }
  509. } else {
  510. lruEntries.remove(entry.key);
  511. journalWriter.append(REMOVE);
  512. journalWriter.append(' ');
  513. journalWriter.append(entry.key);
  514. journalWriter.append('\n');
  515. }
  516. journalWriter.flush();
  517. // 判断是否需要清理磁盘空间
  518. if (size > maxSize || journalRebuildRequired()) {
  519. executorService.submit(cleanupCallable);
  520. }
  521. }
  522. /**
  523. * 只有当日志大小减半并消除至少2000个ops时,我们才重新生成日志。
  524. */
  525. private boolean journalRebuildRequired() {
  526. final int redundantOpCompactThreshold = 2000;
  527. return redundantOpCount >= redundantOpCompactThreshold //
  528. && redundantOpCount >= lruEntries.size();
  529. }
  530. /**
  531. * 删除{@code key}的条目,如果它存在并且可以被删除。正在编辑的条目不能删除。
  532. * @return true if an entry was removed.
  533. */
  534. public synchronized boolean remove(String key) throws IOException {
  535. checkNotClosed();
  536. //取出当前key对应的缓存Entry
  537. Entry entry = lruEntries.get(key);
  538. if (entry == null || entry.currentEditor != null) {
  539. return false;
  540. }
  541. for (int i = 0; i < valueCount; i++) {
  542. File file = entry.getCleanFile(i);
  543. if (file.exists() && !file.delete()) {
  544. throw new IOException("failed to delete " + file);
  545. }
  546. size -= entry.lengths[i];
  547. entry.lengths[i] = 0;
  548. }
  549. redundantOpCount++;
  550. journalWriter.append(REMOVE);
  551. journalWriter.append(' ');
  552. journalWriter.append(key);
  553. journalWriter.append('\n');
  554. lruEntries.remove(key);
  555. if (journalRebuildRequired()) {
  556. executorService.submit(cleanupCallable);
  557. }
  558. return true;
  559. }
  560. /**
  561. * Returns true if this cache has been closed.
  562. */
  563. public synchronized boolean isClosed() {
  564. return journalWriter == null;
  565. }
  566. private void checkNotClosed() {
  567. if (journalWriter == null) {
  568. throw new IllegalStateException("cache is closed");
  569. }
  570. }
  571. /**
  572. * 强制对文件系统进行缓冲操作。
  573. * 这个方法用于将内存中的操作记录同步到日志文件(也就是journal文件)当中。
  574. * 这个方法非常重要,因为DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容。
  575. * 并不是每次写读缓存都要调用一次flush()方法,频繁地调用并不会带来任何好处,只会额外增加同步journal文件的时间。
  576. * 比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了。
  577. */
  578. public synchronized void flush() throws IOException {
  579. checkNotClosed();
  580. trimToSize();
  581. journalWriter.flush();
  582. }
  583. /**
  584. * 这个方法用于将DiskLruCache关闭掉,是和open()方法对应的一个方法。
  585. * 关闭掉了之后就不能再调用DiskLruCache中任何操作缓存数据的方法
  586. * 通常只应该在Activity的onDestroy()方法中去调用close()方法。
  587. */
  588. public synchronized void close() throws IOException {
  589. if (journalWriter == null) {
  590. return; // Already closed.
  591. }
  592. for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
  593. if (entry.currentEditor != null) {
  594. entry.currentEditor.abort();
  595. }
  596. }
  597. trimToSize();
  598. journalWriter.close();
  599. journalWriter = null;
  600. }
  601. private void trimToSize() throws IOException {
  602. while (size > maxSize) {
  603. Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
  604. remove(toEvict.getKey());
  605. }
  606. }
  607. /**
  608. * 关闭缓存并删除其存储的所有值。这将删除缓存目录中的所有文件,包括不是由缓存创建的文件。
  609. */
  610. public void delete() throws IOException {
  611. close();
  612. DiskUtils.deleteContents(directory);
  613. }
  614. private static String inputStreamToString(InputStream in) throws IOException {
  615. //写数据,一次读取一个字节
  616. return DiskUtils.readFully(new InputStreamReader(in, DiskUtils.UTF_8));
  617. }
  618. /**
  619. * A snapshot of the values for an entry.
  620. */
  621. public final class Value {
  622. private final String key;
  623. private final long sequenceNumber;
  624. private final long[] lengths;
  625. private final File[] files;
  626. private Value(String key, long sequenceNumber, File[] files, long[] lengths) {
  627. this.key = key;
  628. this.sequenceNumber = sequenceNumber;
  629. this.files = files;
  630. this.lengths = lengths;
  631. }
  632. /**
  633. * Returns an editor for this snapshot's entry, or null if either the
  634. * entry has changed since this snapshot was created or if another edit
  635. * is in progress.
  636. */
  637. public Editor edit() throws IOException {
  638. return DiskLruCache.this.edit(key, sequenceNumber);
  639. }
  640. public File getFile(int index) {
  641. return files[index];
  642. }
  643. /**
  644. * Returns the string value for {@code index}.
  645. */
  646. public String getString(int index) throws IOException {
  647. InputStream is = new FileInputStream(files[index]);
  648. return inputStreamToString(is);
  649. }
  650. /**
  651. * Returns the byte length of the value for {@code index}.
  652. */
  653. public long getLength(int index) {
  654. return lengths[index];
  655. }
  656. }
  657. /**
  658. * 编辑条目的值。
  659. */
  660. public final class Editor {
  661. private final Entry entry;
  662. private final boolean[] written;
  663. private boolean committed;
  664. private Editor(Entry entry) {
  665. this.entry = entry;
  666. this.written = (entry.readable) ? null : new boolean[valueCount];
  667. }
  668. /**
  669. * 返回一个未缓冲的输入流来读取最后提交的值,如果没有提交值,则返回null。
  670. */
  671. private InputStream newInputStream(int index) throws IOException {
  672. synchronized (DiskLruCache.this) {
  673. if (entry.currentEditor != this) {
  674. throw new IllegalStateException();
  675. }
  676. if (!entry.readable) {
  677. return null;
  678. }
  679. try {
  680. return new FileInputStream(entry.getCleanFile(index));
  681. } catch (FileNotFoundException e) {
  682. return null;
  683. }
  684. }
  685. }
  686. /**
  687. * 以字符串的形式返回最后一个提交的值,如果没有提交值,则返回null。
  688. */
  689. public String getString(int index) throws IOException {
  690. //创建io流对象
  691. InputStream in = newInputStream(index);
  692. //如果流对象不为空,则写入数据
  693. return in != null ? inputStreamToString(in) : null;
  694. }
  695. public File getFile(int index) throws IOException {
  696. synchronized (DiskLruCache.this) {
  697. if (entry.currentEditor != this) {
  698. throw new IllegalStateException();
  699. }
  700. if (!entry.readable) {
  701. written[index] = true;
  702. }
  703. File dirtyFile = entry.getDirtyFile(index);
  704. if (!directory.exists()) {
  705. directory.mkdirs();
  706. }
  707. return dirtyFile;
  708. }
  709. }
  710. /**
  711. * Sets the value at {@code index} to {@code value}.
  712. */
  713. public void set(int index, String value) throws IOException {
  714. Writer writer = null;
  715. try {
  716. File file = getFile(index);
  717. //创建输出流对象
  718. OutputStream os = new FileOutputStream(file);
  719. writer = new OutputStreamWriter(os, DiskUtils.UTF_8);
  720. //写数据
  721. writer.write(value);
  722. } finally {
  723. //关闭流对象
  724. DiskUtils.closeQuietly(writer);
  725. }
  726. }
  727. /**
  728. * 成功后调用Editor.commit()
  729. * 提交此编辑,使其对读者可见。这释放了编辑锁,因此可以在同一键上启动另一个编辑。
  730. */
  731. public void commit() throws IOException {
  732. // The object using this Editor must catch and handle any errors
  733. // during the write. If there is an error and they call commit
  734. // anyway, we will assume whatever they managed to write was valid.
  735. // Normally they should call abort.
  736. completeEdit(this, true);
  737. committed = true;
  738. }
  739. /**
  740. * 失败后调用Editor.abort()方法
  741. * 中止这个编辑。这释放了编辑锁,因此可以在同一键上启动另一个编辑。
  742. */
  743. public void abort() throws IOException {
  744. completeEdit(this, false);
  745. }
  746. public void abortUnlessCommitted() {
  747. if (!committed) {
  748. try {
  749. abort();
  750. } catch (IOException ignored) {
  751. }
  752. }
  753. }
  754. }
  755. private final class Entry {
  756. private final String key;
  757. /**
  758. * Lengths of this entry's files.
  759. */
  760. private final long[] lengths;
  761. /**
  762. * Memoized File objects for this entry to avoid char[] allocations.
  763. */
  764. File[] cleanFiles;
  765. File[] dirtyFiles;
  766. /**
  767. * True if this entry has ever been published.
  768. */
  769. private boolean readable;
  770. /**
  771. * The ongoing edit or null if this entry is not being edited.
  772. */
  773. private Editor currentEditor;
  774. /**
  775. * The sequence number of the most recently committed edit to this entry.
  776. */
  777. private long sequenceNumber;
  778. private Entry(String key) {
  779. this.key = key;
  780. this.lengths = new long[valueCount];
  781. cleanFiles = new File[valueCount];
  782. dirtyFiles = new File[valueCount];
  783. // The names are repetitive so re-use the same builder to avoid allocations.
  784. StringBuilder fileBuilder = new StringBuilder(key).append('.');
  785. int truncateTo = fileBuilder.length();
  786. for (int i = 0; i < valueCount; i++) {
  787. fileBuilder.append(i);
  788. cleanFiles[i] = new File(directory, fileBuilder.toString());
  789. fileBuilder.append(".tmp");
  790. dirtyFiles[i] = new File(directory, fileBuilder.toString());
  791. fileBuilder.setLength(truncateTo);
  792. }
  793. }
  794. public String getLengths() throws IOException {
  795. StringBuilder result = new StringBuilder();
  796. for (long size : lengths) {
  797. result.append(' ').append(size);
  798. }
  799. return result.toString();
  800. }
  801. /**
  802. * Set lengths using decimal numbers like "10123".
  803. */
  804. private void setLengths(String[] strings) throws IOException {
  805. if (strings.length != valueCount) {
  806. throw invalidLengths(strings);
  807. }
  808. try {
  809. for (int i = 0; i < strings.length; i++) {
  810. lengths[i] = Long.parseLong(strings[i]);
  811. }
  812. } catch (NumberFormatException e) {
  813. throw invalidLengths(strings);
  814. }
  815. }
  816. private IOException invalidLengths(String[] strings) throws IOException {
  817. throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
  818. }
  819. public File getCleanFile(int i) {
  820. return cleanFiles[i];
  821. }
  822. public File getDirtyFile(int i) {
  823. return dirtyFiles[i];
  824. }
  825. }
  826. /**
  827. * A {@link ThreadFactory} that builds a thread with a specific thread name
  828. * and with minimum priority.
  829. */
  830. private static final class DiskLruCacheThreadFactory implements ThreadFactory {
  831. @Override
  832. public synchronized Thread newThread(Runnable runnable) {
  833. //设置线程优先级
  834. Thread result = new Thread(runnable, "video-disk-lru-cache-thread");
  835. result.setPriority(Thread.MIN_PRIORITY);
  836. return result;
  837. }
  838. }
  839. }