DiskLruCache.java 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  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. static final String JOURNAL_FILE = "journal";
  35. static final String JOURNAL_FILE_TEMP = "journal.tmp";
  36. static final String JOURNAL_FILE_BACKUP = "journal.bkp";
  37. static final String MAGIC = "libcore.io.DiskLruCache";
  38. static final String VERSION_1 = "1";
  39. static final long ANY_SEQUENCE_NUMBER = -1;
  40. private static final String CLEAN = "CLEAN";
  41. private static final String DIRTY = "DIRTY";
  42. private static final String REMOVE = "REMOVE";
  43. private static final String READ = "READ";
  44. private final File directory;
  45. private final File journalFile;
  46. private final File journalFileTmp;
  47. private final File journalFileBackup;
  48. private final int appVersion;
  49. private long maxSize;
  50. private final int valueCount;
  51. private long size = 0;
  52. private Writer journalWriter;
  53. private final LinkedHashMap<String, Entry> lruEntries =
  54. new LinkedHashMap<String, Entry>(0, 0.75f, true);
  55. private int redundantOpCount;
  56. /**
  57. * To differentiate between old and current snapshots, each entry is given
  58. * a sequence number each time an edit is committed. A snapshot is stale if
  59. * its sequence number is not equal to its entry's sequence number.
  60. */
  61. private long nextSequenceNumber = 0;
  62. /**
  63. * This cache uses a single background thread to evict entries.
  64. */
  65. final ThreadPoolExecutor executorService =
  66. new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
  67. new DiskLruCacheThreadFactory());
  68. private final Callable<Void> cleanupCallable = new Callable<Void>() {
  69. public Void call() throws Exception {
  70. synchronized (DiskLruCache.this) {
  71. if (journalWriter == null) {
  72. return null; // Closed.
  73. }
  74. trimToSize();
  75. if (journalRebuildRequired()) {
  76. rebuildJournal();
  77. redundantOpCount = 0;
  78. }
  79. }
  80. return null;
  81. }
  82. };
  83. private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
  84. this.directory = directory;
  85. this.appVersion = appVersion;
  86. this.journalFile = new File(directory, JOURNAL_FILE);
  87. this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
  88. this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
  89. this.valueCount = valueCount;
  90. this.maxSize = maxSize;
  91. }
  92. /**
  93. * Opens the cache in {@code directory}, creating a cache if none exists
  94. * there.
  95. * 第一个参数表示磁盘缓存在文件系统中的存储路径。
  96. * 第二个参数表示应用的版本号,一般设为 1 即可。当版本号发生改变时 DiskLruCache 会清空之前所有的缓存文件,
  97. * 而这个特性在实际开发中作用并不大,很多情况下即使应用的版本号发生了改变缓存文件却仍然是有效的,因此这个参数设为 1 比较好。
  98. * 第三个参数表示同一个 key 可以对应多少个缓存文件,一般设为 1 即可。
  99. * 第四个参数表示缓存的总大小,比如 50MB,当缓存大小超出这个设定值后,DiskLruCache 会清除一些缓存从而保证总大小不大于这个设定值。
  100. * @param directory a writable directory
  101. * @param valueCount the number of values per cache entry. Must be positive.
  102. * @param maxSize the maximum number of bytes this cache should use to store
  103. * @throws IOException if reading or writing the cache directory fails
  104. */
  105. public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
  106. throws IOException {
  107. if (maxSize <= 0) {
  108. throw new IllegalArgumentException("maxSize <= 0");
  109. }
  110. if (valueCount <= 0) {
  111. throw new IllegalArgumentException("valueCount <= 0");
  112. }
  113. // If a bkp file exists, use it instead.
  114. File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
  115. if (backupFile.exists()) {
  116. File journalFile = new File(directory, JOURNAL_FILE);
  117. // If journal file also exists just delete backup file.
  118. if (journalFile.exists()) {
  119. backupFile.delete();
  120. } else {
  121. renameTo(backupFile, journalFile, false);
  122. }
  123. }
  124. // Prefer to pick up where we left off.
  125. DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
  126. if (cache.journalFile.exists()) {
  127. try {
  128. cache.readJournal();
  129. cache.processJournal();
  130. return cache;
  131. } catch (IOException journalIsCorrupt) {
  132. System.out
  133. .println("DiskLruCache "
  134. + directory
  135. + " is corrupt: "
  136. + journalIsCorrupt.getMessage()
  137. + ", removing");
  138. cache.delete();
  139. }
  140. }
  141. // Create a new empty cache.
  142. directory.mkdirs();
  143. cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
  144. cache.rebuildJournal();
  145. return cache;
  146. }
  147. private void readJournal() throws IOException {
  148. StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), DiskUtils.US_ASCII);
  149. try {
  150. String magic = reader.readLine();
  151. String version = reader.readLine();
  152. String appVersionString = reader.readLine();
  153. String valueCountString = reader.readLine();
  154. String blank = reader.readLine();
  155. if (!MAGIC.equals(magic)
  156. || !VERSION_1.equals(version)
  157. || !Integer.toString(appVersion).equals(appVersionString)
  158. || !Integer.toString(valueCount).equals(valueCountString)
  159. || !"".equals(blank)) {
  160. throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
  161. + valueCountString + ", " + blank + "]");
  162. }
  163. int lineCount = 0;
  164. while (true) {
  165. try {
  166. readJournalLine(reader.readLine());
  167. lineCount++;
  168. } catch (EOFException endOfJournal) {
  169. break;
  170. }
  171. }
  172. redundantOpCount = lineCount - lruEntries.size();
  173. // If we ended on a truncated line, rebuild the journal before appending to it.
  174. if (reader.hasUnterminatedLine()) {
  175. rebuildJournal();
  176. } else {
  177. journalWriter = new BufferedWriter(new OutputStreamWriter(
  178. new FileOutputStream(journalFile, true), DiskUtils.US_ASCII));
  179. }
  180. } finally {
  181. DiskUtils.closeQuietly(reader);
  182. }
  183. }
  184. private void readJournalLine(String line) throws IOException {
  185. int firstSpace = line.indexOf(' ');
  186. if (firstSpace == -1) {
  187. throw new IOException("unexpected journal line: " + line);
  188. }
  189. int keyBegin = firstSpace + 1;
  190. int secondSpace = line.indexOf(' ', keyBegin);
  191. final String key;
  192. if (secondSpace == -1) {
  193. key = line.substring(keyBegin);
  194. if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
  195. lruEntries.remove(key);
  196. return;
  197. }
  198. } else {
  199. key = line.substring(keyBegin, secondSpace);
  200. }
  201. Entry entry = lruEntries.get(key);
  202. if (entry == null) {
  203. entry = new Entry(key);
  204. lruEntries.put(key, entry);
  205. }
  206. if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
  207. String[] parts = line.substring(secondSpace + 1).split(" ");
  208. entry.readable = true;
  209. entry.currentEditor = null;
  210. entry.setLengths(parts);
  211. } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
  212. entry.currentEditor = new Editor(entry);
  213. } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
  214. // This work was already done by calling lruEntries.get().
  215. } else {
  216. throw new IOException("unexpected journal line: " + line);
  217. }
  218. }
  219. /**
  220. * Computes the initial size and collects garbage as a part of opening the
  221. * cache. Dirty entries are assumed to be inconsistent and will be deleted.
  222. */
  223. private void processJournal() throws IOException {
  224. deleteIfExists(journalFileTmp);
  225. for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
  226. Entry entry = i.next();
  227. if (entry.currentEditor == null) {
  228. for (int t = 0; t < valueCount; t++) {
  229. size += entry.lengths[t];
  230. }
  231. } else {
  232. entry.currentEditor = null;
  233. for (int t = 0; t < valueCount; t++) {
  234. deleteIfExists(entry.getCleanFile(t));
  235. deleteIfExists(entry.getDirtyFile(t));
  236. }
  237. i.remove();
  238. }
  239. }
  240. }
  241. /**
  242. * Creates a new journal that omits redundant information. This replaces the
  243. * current journal if it exists.
  244. */
  245. private synchronized void rebuildJournal() throws IOException {
  246. if (journalWriter != null) {
  247. journalWriter.close();
  248. }
  249. Writer writer = new BufferedWriter(
  250. new OutputStreamWriter(new FileOutputStream(journalFileTmp), DiskUtils.US_ASCII));
  251. try {
  252. writer.write(MAGIC);
  253. writer.write("\n");
  254. writer.write(VERSION_1);
  255. writer.write("\n");
  256. writer.write(Integer.toString(appVersion));
  257. writer.write("\n");
  258. writer.write(Integer.toString(valueCount));
  259. writer.write("\n");
  260. writer.write("\n");
  261. for (Entry entry : lruEntries.values()) {
  262. if (entry.currentEditor != null) {
  263. writer.write(DIRTY + ' ' + entry.key + '\n');
  264. } else {
  265. writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
  266. }
  267. }
  268. } finally {
  269. writer.close();
  270. }
  271. if (journalFile.exists()) {
  272. renameTo(journalFile, journalFileBackup, true);
  273. }
  274. renameTo(journalFileTmp, journalFile, false);
  275. journalFileBackup.delete();
  276. journalWriter = new BufferedWriter(
  277. new OutputStreamWriter(new FileOutputStream(journalFile, true), DiskUtils.US_ASCII));
  278. }
  279. private static void deleteIfExists(File file) throws IOException {
  280. if (file.exists() && !file.delete()) {
  281. throw new IOException();
  282. }
  283. }
  284. private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
  285. if (deleteDestination) {
  286. deleteIfExists(to);
  287. }
  288. if (!from.renameTo(to)) {
  289. throw new IOException();
  290. }
  291. }
  292. /**
  293. * Returns a snapshot of the entry named {@code key}, or null if it doesn't
  294. * exist is not currently readable. If a value is returned, it is moved to
  295. * the head of the LRU queue.
  296. */
  297. public synchronized Value get(String key) throws IOException {
  298. checkNotClosed();
  299. Entry entry = lruEntries.get(key);
  300. if (entry == null) {
  301. return null;
  302. }
  303. if (!entry.readable) {
  304. return null;
  305. }
  306. for (File file : entry.cleanFiles) {
  307. // A file must have been deleted manually!
  308. if (!file.exists()) {
  309. return null;
  310. }
  311. }
  312. redundantOpCount++;
  313. journalWriter.append(READ);
  314. journalWriter.append(' ');
  315. journalWriter.append(key);
  316. journalWriter.append('\n');
  317. if (journalRebuildRequired()) {
  318. executorService.submit(cleanupCallable);
  319. }
  320. return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
  321. }
  322. /**
  323. * Returns an editor for the entry named {@code key}, or null if another
  324. * edit is in progress.
  325. */
  326. public Editor edit(String key) throws IOException {
  327. return edit(key, ANY_SEQUENCE_NUMBER);
  328. }
  329. private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
  330. checkNotClosed();
  331. Entry entry = lruEntries.get(key);
  332. if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
  333. || entry.sequenceNumber != expectedSequenceNumber)) {
  334. return null; // Value is stale.
  335. }
  336. if (entry == null) {
  337. entry = new Entry(key);
  338. lruEntries.put(key, entry);
  339. } else if (entry.currentEditor != null) {
  340. return null; // Another edit is in progress.
  341. }
  342. Editor editor = new Editor(entry);
  343. entry.currentEditor = editor;
  344. // Flush the journal before creating files to prevent file leaks.
  345. journalWriter.append(DIRTY);
  346. journalWriter.append(' ');
  347. journalWriter.append(key);
  348. journalWriter.append('\n');
  349. journalWriter.flush();
  350. return editor;
  351. }
  352. /**
  353. * Returns the directory where this cache stores its data.
  354. */
  355. public File getDirectory() {
  356. return directory;
  357. }
  358. /**
  359. * Returns the maximum number of bytes that this cache should use to store
  360. * its data.
  361. */
  362. public synchronized long getMaxSize() {
  363. return maxSize;
  364. }
  365. /**
  366. * Changes the maximum number of bytes the cache can store and queues a job
  367. * to trim the existing store, if necessary.
  368. */
  369. public synchronized void setMaxSize(long maxSize) {
  370. this.maxSize = maxSize;
  371. executorService.submit(cleanupCallable);
  372. }
  373. /**
  374. * Returns the number of bytes currently being used to store the values in
  375. * this cache. This may be greater than the max size if a background
  376. * deletion is pending.
  377. */
  378. public synchronized long size() {
  379. return size;
  380. }
  381. private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
  382. Entry entry = editor.entry;
  383. if (entry.currentEditor != editor) {
  384. throw new IllegalStateException();
  385. }
  386. // If this edit is creating the entry for the first time, every index must have a value.
  387. if (success && !entry.readable) {
  388. for (int i = 0; i < valueCount; i++) {
  389. if (!editor.written[i]) {
  390. editor.abort();
  391. throw new IllegalStateException("Newly created entry didn't create value for index " + i);
  392. }
  393. if (!entry.getDirtyFile(i).exists()) {
  394. editor.abort();
  395. return;
  396. }
  397. }
  398. }
  399. for (int i = 0; i < valueCount; i++) {
  400. File dirty = entry.getDirtyFile(i);
  401. if (success) {
  402. if (dirty.exists()) {
  403. File clean = entry.getCleanFile(i);
  404. dirty.renameTo(clean);
  405. long oldLength = entry.lengths[i];
  406. long newLength = clean.length();
  407. entry.lengths[i] = newLength;
  408. size = size - oldLength + newLength;
  409. }
  410. } else {
  411. deleteIfExists(dirty);
  412. }
  413. }
  414. redundantOpCount++;
  415. entry.currentEditor = null;
  416. if (entry.readable | success) {
  417. entry.readable = true;
  418. journalWriter.append(CLEAN);
  419. journalWriter.append(' ');
  420. journalWriter.append(entry.key);
  421. journalWriter.append(entry.getLengths());
  422. journalWriter.append('\n');
  423. if (success) {
  424. entry.sequenceNumber = nextSequenceNumber++;
  425. }
  426. } else {
  427. lruEntries.remove(entry.key);
  428. journalWriter.append(REMOVE);
  429. journalWriter.append(' ');
  430. journalWriter.append(entry.key);
  431. journalWriter.append('\n');
  432. }
  433. journalWriter.flush();
  434. if (size > maxSize || journalRebuildRequired()) {
  435. executorService.submit(cleanupCallable);
  436. }
  437. }
  438. /**
  439. * We only rebuild the journal when it will halve the size of the journal
  440. * and eliminate at least 2000 ops.
  441. */
  442. private boolean journalRebuildRequired() {
  443. final int redundantOpCompactThreshold = 2000;
  444. return redundantOpCount >= redundantOpCompactThreshold //
  445. && redundantOpCount >= lruEntries.size();
  446. }
  447. /**
  448. * Drops the entry for {@code key} if it exists and can be removed. Entries
  449. * actively being edited cannot be removed.
  450. *
  451. * @return true if an entry was removed.
  452. */
  453. public synchronized boolean remove(String key) throws IOException {
  454. checkNotClosed();
  455. Entry entry = lruEntries.get(key);
  456. if (entry == null || entry.currentEditor != null) {
  457. return false;
  458. }
  459. for (int i = 0; i < valueCount; i++) {
  460. File file = entry.getCleanFile(i);
  461. if (file.exists() && !file.delete()) {
  462. throw new IOException("failed to delete " + file);
  463. }
  464. size -= entry.lengths[i];
  465. entry.lengths[i] = 0;
  466. }
  467. redundantOpCount++;
  468. journalWriter.append(REMOVE);
  469. journalWriter.append(' ');
  470. journalWriter.append(key);
  471. journalWriter.append('\n');
  472. lruEntries.remove(key);
  473. if (journalRebuildRequired()) {
  474. executorService.submit(cleanupCallable);
  475. }
  476. return true;
  477. }
  478. /**
  479. * Returns true if this cache has been closed.
  480. */
  481. public synchronized boolean isClosed() {
  482. return journalWriter == null;
  483. }
  484. private void checkNotClosed() {
  485. if (journalWriter == null) {
  486. throw new IllegalStateException("cache is closed");
  487. }
  488. }
  489. /**
  490. * Force buffered operations to the filesystem.
  491. */
  492. public synchronized void flush() throws IOException {
  493. checkNotClosed();
  494. trimToSize();
  495. journalWriter.flush();
  496. }
  497. /**
  498. * Closes this cache. Stored values will remain on the filesystem.
  499. */
  500. public synchronized void close() throws IOException {
  501. if (journalWriter == null) {
  502. return; // Already closed.
  503. }
  504. for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
  505. if (entry.currentEditor != null) {
  506. entry.currentEditor.abort();
  507. }
  508. }
  509. trimToSize();
  510. journalWriter.close();
  511. journalWriter = null;
  512. }
  513. private void trimToSize() throws IOException {
  514. while (size > maxSize) {
  515. Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
  516. remove(toEvict.getKey());
  517. }
  518. }
  519. /**
  520. * Closes the cache and deletes all of its stored values. This will delete
  521. * all files in the cache directory including files that weren't created by
  522. * the cache.
  523. */
  524. public void delete() throws IOException {
  525. close();
  526. DiskUtils.deleteContents(directory);
  527. }
  528. private static String inputStreamToString(InputStream in) throws IOException {
  529. return DiskUtils.readFully(new InputStreamReader(in, DiskUtils.UTF_8));
  530. }
  531. /**
  532. * A snapshot of the values for an entry.
  533. */
  534. public final class Value {
  535. private final String key;
  536. private final long sequenceNumber;
  537. private final long[] lengths;
  538. private final File[] files;
  539. private Value(String key, long sequenceNumber, File[] files, long[] lengths) {
  540. this.key = key;
  541. this.sequenceNumber = sequenceNumber;
  542. this.files = files;
  543. this.lengths = lengths;
  544. }
  545. /**
  546. * Returns an editor for this snapshot's entry, or null if either the
  547. * entry has changed since this snapshot was created or if another edit
  548. * is in progress.
  549. */
  550. public Editor edit() throws IOException {
  551. return DiskLruCache.this.edit(key, sequenceNumber);
  552. }
  553. public File getFile(int index) {
  554. return files[index];
  555. }
  556. /**
  557. * Returns the string value for {@code index}.
  558. */
  559. public String getString(int index) throws IOException {
  560. InputStream is = new FileInputStream(files[index]);
  561. return inputStreamToString(is);
  562. }
  563. /**
  564. * Returns the byte length of the value for {@code index}.
  565. */
  566. public long getLength(int index) {
  567. return lengths[index];
  568. }
  569. }
  570. /**
  571. * Edits the values for an entry.
  572. */
  573. public final class Editor {
  574. private final Entry entry;
  575. private final boolean[] written;
  576. private boolean committed;
  577. private Editor(Entry entry) {
  578. this.entry = entry;
  579. this.written = (entry.readable) ? null : new boolean[valueCount];
  580. }
  581. /**
  582. * Returns an unbuffered input stream to read the last committed value,
  583. * or null if no value has been committed.
  584. */
  585. private InputStream newInputStream(int index) throws IOException {
  586. synchronized (DiskLruCache.this) {
  587. if (entry.currentEditor != this) {
  588. throw new IllegalStateException();
  589. }
  590. if (!entry.readable) {
  591. return null;
  592. }
  593. try {
  594. return new FileInputStream(entry.getCleanFile(index));
  595. } catch (FileNotFoundException e) {
  596. return null;
  597. }
  598. }
  599. }
  600. /**
  601. * Returns the last committed value as a string, or null if no value
  602. * has been committed.
  603. */
  604. public String getString(int index) throws IOException {
  605. InputStream in = newInputStream(index);
  606. return in != null ? inputStreamToString(in) : null;
  607. }
  608. public File getFile(int index) throws IOException {
  609. synchronized (DiskLruCache.this) {
  610. if (entry.currentEditor != this) {
  611. throw new IllegalStateException();
  612. }
  613. if (!entry.readable) {
  614. written[index] = true;
  615. }
  616. File dirtyFile = entry.getDirtyFile(index);
  617. if (!directory.exists()) {
  618. directory.mkdirs();
  619. }
  620. return dirtyFile;
  621. }
  622. }
  623. /**
  624. * Sets the value at {@code index} to {@code value}.
  625. */
  626. public void set(int index, String value) throws IOException {
  627. Writer writer = null;
  628. try {
  629. OutputStream os = new FileOutputStream(getFile(index));
  630. writer = new OutputStreamWriter(os, DiskUtils.UTF_8);
  631. writer.write(value);
  632. } finally {
  633. DiskUtils.closeQuietly(writer);
  634. }
  635. }
  636. /**
  637. * Commits this edit so it is visible to readers. This releases the
  638. * edit lock so another edit may be started on the same key.
  639. */
  640. public void commit() throws IOException {
  641. // The object using this Editor must catch and handle any errors
  642. // during the write. If there is an error and they call commit
  643. // anyway, we will assume whatever they managed to write was valid.
  644. // Normally they should call abort.
  645. completeEdit(this, true);
  646. committed = true;
  647. }
  648. /**
  649. * Aborts this edit. This releases the edit lock so another edit may be
  650. * started on the same key.
  651. */
  652. public void abort() throws IOException {
  653. completeEdit(this, false);
  654. }
  655. public void abortUnlessCommitted() {
  656. if (!committed) {
  657. try {
  658. abort();
  659. } catch (IOException ignored) {
  660. }
  661. }
  662. }
  663. }
  664. private final class Entry {
  665. private final String key;
  666. /**
  667. * Lengths of this entry's files.
  668. */
  669. private final long[] lengths;
  670. /**
  671. * Memoized File objects for this entry to avoid char[] allocations.
  672. */
  673. File[] cleanFiles;
  674. File[] dirtyFiles;
  675. /**
  676. * True if this entry has ever been published.
  677. */
  678. private boolean readable;
  679. /**
  680. * The ongoing edit or null if this entry is not being edited.
  681. */
  682. private Editor currentEditor;
  683. /**
  684. * The sequence number of the most recently committed edit to this entry.
  685. */
  686. private long sequenceNumber;
  687. private Entry(String key) {
  688. this.key = key;
  689. this.lengths = new long[valueCount];
  690. cleanFiles = new File[valueCount];
  691. dirtyFiles = new File[valueCount];
  692. // The names are repetitive so re-use the same builder to avoid allocations.
  693. StringBuilder fileBuilder = new StringBuilder(key).append('.');
  694. int truncateTo = fileBuilder.length();
  695. for (int i = 0; i < valueCount; i++) {
  696. fileBuilder.append(i);
  697. cleanFiles[i] = new File(directory, fileBuilder.toString());
  698. fileBuilder.append(".tmp");
  699. dirtyFiles[i] = new File(directory, fileBuilder.toString());
  700. fileBuilder.setLength(truncateTo);
  701. }
  702. }
  703. public String getLengths() throws IOException {
  704. StringBuilder result = new StringBuilder();
  705. for (long size : lengths) {
  706. result.append(' ').append(size);
  707. }
  708. return result.toString();
  709. }
  710. /**
  711. * Set lengths using decimal numbers like "10123".
  712. */
  713. private void setLengths(String[] strings) throws IOException {
  714. if (strings.length != valueCount) {
  715. throw invalidLengths(strings);
  716. }
  717. try {
  718. for (int i = 0; i < strings.length; i++) {
  719. lengths[i] = Long.parseLong(strings[i]);
  720. }
  721. } catch (NumberFormatException e) {
  722. throw invalidLengths(strings);
  723. }
  724. }
  725. private IOException invalidLengths(String[] strings) throws IOException {
  726. throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
  727. }
  728. public File getCleanFile(int i) {
  729. return cleanFiles[i];
  730. }
  731. public File getDirtyFile(int i) {
  732. return dirtyFiles[i];
  733. }
  734. }
  735. /**
  736. * A {@link ThreadFactory} that builds a thread with a specific thread name
  737. * and with minimum priority.
  738. */
  739. private static final class DiskLruCacheThreadFactory implements ThreadFactory {
  740. @Override
  741. public synchronized Thread newThread(Runnable runnable) {
  742. Thread result = new Thread(runnable, "glide-disk-lru-cache-thread");
  743. result.setPriority(Thread.MIN_PRIORITY);
  744. return result;
  745. }
  746. }
  747. }