1 /*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18 package com.example.android.bitmapfun.util;
19
20
21 import java.io.BufferedInputStream;
22 import java.io.BufferedWriter;
23 import java.io.Closeable;
24 import java.io.EOFException;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileNotFoundException;
28 import java.io.FileOutputStream;
29 import java.io.FileWriter;
30 import java.io.FilterOutputStream;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.InputStreamReader;
34 import java.io.OutputStream;
35 import java.io.OutputStreamWriter;
36 import java.io.Reader;
37 import java.io.StringWriter;
38 import java.io.Writer;
39 import java.lang.reflect.Array;
40 import java.nio.charset.Charset;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Iterator;
44 import java.util.LinkedHashMap;
45 import java.util.Map;
46 import java.util.concurrent.Callable;
47 import java.util.concurrent.ExecutorService;
48 import java.util.concurrent.LinkedBlockingQueue;
49 import java.util.concurrent.ThreadPoolExecutor;
50 import java.util.concurrent.TimeUnit;
51
52
53 /**
54 ******************************************************************************
55 * Taken from the JB source code, can be found in:
56 * libcore/luni/src/main/java/libcore/io/DiskLruCache.java
57 * or direct link:
58 * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
59 ******************************************************************************
60 *
61 * A cache that uses a bounded amount of space on a filesystem. Each cache
62 * entry has a string key and a fixed number of values. Values are byte
63 * sequences, accessible as streams or files. Each value must be between {@code
64 * 0} and {@code Integer.MAX_VALUE} bytes in length.
65 * 一个使用空间大小有边界的文件cache,每一个entry包含一个key和values。values是byte序列,按文件或者流来访问的。
66 * 每一个value的长度在0---Integer.MAX_VALUE之间。
67 *
68 * <p>The cache stores its data in a directory on the filesystem. This
69 * directory must be exclusive to the cache; the cache may delete or overwrite
70 * files from its directory. It is an error for multiple processes to use the
71 * same cache directory at the same time.
72 * cache使用目录文件存储数据。文件路径必须是唯一的,可以删除和重写目录文件。多个进程同时使用同样的文件目录是不正确的
73 *
74 * <p>This cache limits the number of bytes that it will store on the
75 * filesystem. When the number of stored bytes exceeds the limit, the cache will
76 * remove entries in the background until the limit is satisfied. The limit is
77 * not strict: the cache may temporarily exceed it while waiting for files to be
78 * deleted. The limit does not include filesystem overhead or the cache
79 * journal so space-sensitive applications should set a conservative limit.
80 * cache限制了大小,当超出空间大小时,cache就会后台删除entry直到空间没有达到上限为止。空间大小限制不是严格的,
81 * cache可能会暂时超过limit在等待文件删除的过程中。cache的limit不包括文件系统的头部和日志,
82 * 所以空间大小敏感的应用应当设置一个保守的limit大小
83 *
84 * <p>Clients call {@link #edit} to create or update the values of an entry. An
85 * entry may have only one editor at one time; if a value is not available to be
86 * edited then {@link #edit} will return null.
87 * <ul>
88 * <li>When an entry is being <strong>created</strong> it is necessary to
89 * supply a full set of values; the empty value should be used as a
90 * placeholder if necessary.
91 * <li>When an entry is being <strong>edited</strong>, it is not necessary
92 * to supply data for every value; values default to their previous
93 * value.
94 * </ul>
95 * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
96 * or {@link Editor#abort}. Committing is atomic: a read observes the full set
97 * of values as they were before or after the commit, but never a mix of values.
98 *调用edit()来创建或者更新entry的值,一个entry同时只能有一个editor;如果值不可被编辑就返回null。
99 *当entry被创建时必须提供一个value。空的value应当用占位符表示。当entry被编辑的时候,必须提供value。
100 *每次调用必须有匹配Editor commit或abort,commit是原子操作,读必须在commit前或者后,不会造成值混乱。
101 *
102 * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
103 * observe the value at the time that {@link #get} was called. Updates and
104 * removals after the call do not impact ongoing reads.
105 * 调用get来读entry的快照。当get调用时读者读其值,更新或者删除不会影响先前的读
106 *
107 * <p>This class is tolerant of some I/O errors. If files are missing from the
108 * filesystem, the corresponding entries will be dropped from the cache. If
109 * an error occurs while writing a cache value, the edit will fail silently.
110 * Callers should handle other problems by catching {@code IOException} and
111 * responding appropriately.
112 * 该类可以容忍一些I/O errors。如果文件丢失啦,相应的entry就会被drop。写cache时如果error发生,edit将失败。
113 * 调用者应当相应的处理其它问题
114 */
115 public final class DiskLruCache implements Closeable {
116 static final String JOURNAL_FILE = "journal";
117 static final String JOURNAL_FILE_TMP = "journal.tmp";
118 static final String MAGIC = "libcore.io.DiskLruCache";
119 static final String VERSION_1 = "1";
120 static final long ANY_SEQUENCE_NUMBER = -1;
121 private static final String CLEAN = "CLEAN";
122 private static final String DIRTY = "DIRTY";
123 private static final String REMOVE = "REMOVE";
124 private static final String READ = "READ";
125
126
127 private static final Charset UTF_8 = Charset.forName("UTF-8");
128 private static final int IO_BUFFER_SIZE = 8 * 1024;//8K
129
130
131 /*
132 * This cache uses a journal file named "journal". A typical journal file
133 * looks like this:
134 * libcore.io.DiskLruCache
135 * 1 //the disk cache's version
136 * 100 //the application's version
137 * 2 //value count
138 *
139 * //state key optional
140 * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
141 * DIRTY 335c4c6028171cfddfbaae1a9c313c52
142 * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
143 * REMOVE 335c4c6028171cfddfbaae1a9c313c52
144 * DIRTY 1ab96a171faeeee38496d8b330771a7a
145 * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
146 * READ 335c4c6028171cfddfbaae1a9c313c52
147 * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
148 *
149 * The first five lines of the journal form its header. They are the
150 * constant string "libcore.io.DiskLruCache", the disk cache's version,
151 * the application's version, the value count, and a blank line.
152 *
153 * Each of the subsequent lines in the file is a record of the state of a
154 * cache entry. Each line contains space-separated values: a state, a key,
155 * and optional state-specific values.
156 * o DIRTY lines track that an entry is actively being created or updated.
157 * Every successful DIRTY action should be followed by a CLEAN or REMOVE
158 * action. DIRTY lines without a matching CLEAN or REMOVE indicate that
159 * temporary files may need to be deleted.
160 * Dirty是entry被创建或者更新,每一个dirty应当被clean或remove action,如果有一行dirty没有
161 * 匹配的clean或Remove action,就表示临时文件需要被删除。
162 * o CLEAN lines track a cache entry that has been successfully published
163 * and may be read. A publish line is followed by the lengths of each of
164 * its values.
165 * Clean entry已经成功的发布并且可能会被读过。一个发布行
166 * o READ lines track accesses for LRU.
167 * Read表示LRU访问
168 * o REMOVE lines track entries that have been deleted.
169 * Remove表示entry已经被删除
170 *
171 * The journal file is appended to as cache operations occur. The journal may
172 * occasionally be compacted by dropping redundant lines. A temporary file named
173 * "journal.tmp" will be used during compaction; that file should be deleted if
174 * it exists when the cache is opened.
175 * 日志文件在cache操作发生时添加,日志可能O尔删除的冗余行来压缩。一个临时的名字为journal.tmp的文件将被使用
176 * 在压缩期间。当cache被opened的时候文件应当被删除。
177 */
178
179
180 private final File directory;
181 private final File journalFile;//日志文件
182 private final File journalFileTmp;//日志文件临时文件
183 private final int appVersion;//应用ersion
184 private final long maxSize;//最大空间
185 private final int valueCount;//key对应的value的个数
186 private long size = 0;
187 private Writer journalWriter;
188 private final LinkedHashMap<String, Entry> lruEntries
189 = new LinkedHashMap<String, Entry>(0, 0.75f, true);
190 private int redundantOpCount;
191
192
193 /**
194 * To differentiate between old and current snapshots, each entry is given
195 * a sequence number each time an edit is committed. A snapshot is stale if
196 * its sequence number is not equal to its entry's sequence number.
197 * 区分老的和当前的快照,每一个实体在每次编辑被committed时都被赋予一个序列号。
198 * 一个快照的序列号如果不等于entry的序列号那它就是废弃的。
199 */
200 private long nextSequenceNumber = 0;
201
202
203 //数组拷贝
204 /* From java.util.Arrays */
205 @SuppressWarnings("unchecked")
206 private static <T> T[] copyOfRange(T[] original, int start, int end) {
207 final int originalLength = original.length; // For exception priority compatibility.
208 if (start > end) {
209 throw new IllegalArgumentException();
210 }
211 if (start < 0 || start > originalLength) {
212 throw new ArrayIndexOutOfBoundsException();
213 }
214 final int resultLength = end - start;
215 final int copyLength = Math.min(resultLength, originalLength - start);
216 final T[] result = (T[]) Array
217 .newInstance(original.getClass().getComponentType(), resultLength);
218 System.arraycopy(original, start, result, 0, copyLength);
219 return result;
220 }
221
222
223 /**
224 * Returns the remainder of 'reader' as a string, closing it when done.
225 * 返回String的值,然后close
226 */
227 public static String readFully(Reader reader) throws IOException {
228 try {
229 StringWriter writer = new StringWriter();
230 char[] buffer = new char[1024];
231 int count;
232 while ((count = reader.read(buffer)) != -1) {
233 writer.write(buffer, 0, count);
234 }
235 return writer.toString();
236 } finally {
237 reader.close();
238 }
239 }
240
241
242 /**
243 * Returns the ASCII characters up to but not including the next "\r\n", or
244 * "\n".
245 *
246 * @throws java.io.EOFException if the stream is exhausted before the next newline
247 * character.
248 * 读取输入流中返回的某行ASCII码字符
249 */
250 public static String readAsciiLine(InputStream in) throws IOException {
251 // TODO: support UTF-8 here instead
252
253
254 StringBuilder result = new StringBuilder(80);
255 while (true) {
256 int c = in.read();
257 if (c == -1) {
258 throw new EOFException();
259 } else if (c == '\n') {
260 break;
261 }
262
263
264 result.append((char) c);
265 }
266 int length = result.length();
267 if (length > 0 && result.charAt(length - 1) == '\r') {
268 result.setLength(length - 1);
269 }
270 return result.toString();
271 }
272
273
274 /**
275 * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
276 * closeable关闭
277 */
278 public static void closeQuietly(Closeable closeable) {
279 if (closeable != null) {
280 try {
281 closeable.close();
282 } catch (RuntimeException rethrown) {
283 throw rethrown;
284 } catch (Exception ignored) {
285 }
286 }
287 }
288
289
290 /**
291 * Recursively delete everything in {@code dir}.
292 * 递归删除dir
293 */
294 // TODO: this should specify paths as Strings rather than as Files
295 public static void deleteContents(File dir) throws IOException {
296 File[] files = dir.listFiles();
297 if (files == null) {
298 throw new IllegalArgumentException("not a directory: " + dir);
299 }
300 for (File file : files) {
301 if (file.isDirectory()) {
302 deleteContents(file);
303 }
304 if (!file.delete()) {
305 throw new IOException("failed to delete file: " + file);
306 }
307 }
308 }
309
310
311 /** This cache uses a single background thread to evict entries.
312 * 后台单线程回收entry
313 */
314 private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
315 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
316 private final Callable<Void> cleanupCallable = new Callable<Void>() {
317 @Override public Void call() throws Exception {
318 synchronized (DiskLruCache.this) {
319 if (journalWriter == null) {
320 return null; // closed
321 }
322 trimToSize();//回收到满足maxsize
323 if (journalRebuildRequired()) {
324 rebuildJournal();
325 redundantOpCount = 0;
326 }
327 }
328 return null;
329 }
330 };
331
332
333 //构造器
334 private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
335 this.directory = directory;
336 this.appVersion = appVersion;
337 this.journalFile = new File(directory, JOURNAL_FILE);
338 this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
339 this.valueCount = valueCount;
340 this.maxSize = maxSize;
341 }
342
343
344 /**
345 * Opens the cache in {@code directory}, creating a cache if none exists
346 * there.
347 * 创建cache
348 *
349 * @param directory a writable directory
350 * @param appVersion
351 * @param valueCount the number of values per cache entry. Must be positive.
352 * 每一个key相对应的value的数目
353 * @param maxSize the maximum number of bytes this cache should use to store
354 * @throws IOException if reading or writing the cache directory fails
355 */
356 public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
357 throws IOException {
358 if (maxSize <= 0) {//maxsize必须大于0
359 throw new IllegalArgumentException("maxSize <= 0");
360 }
361 if (valueCount <= 0) {//valuecount也必须大于0
362 throw new IllegalArgumentException("valueCount <= 0");
363 }
364
365
366 // prefer to pick up where we left off优先处理先前的cache
367 DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
368 if (cache.journalFile.exists()) {
369 try {
370 cache.readJournal();
371 cache.processJournal();
372 cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
373 IO_BUFFER_SIZE);
374 return cache;
375 } catch (IOException journalIsCorrupt) {
376 // System.logW("DiskLruCache " + directory + " is corrupt: "
377 // + journalIsCorrupt.getMessage() + ", removing");
378 cache.delete();
379 }
380 }
381
382
383 // create a new empty cache创建一个空新的cache
384 directory.mkdirs();
385 cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
386 cache.rebuildJournal();
387 return cache;
388 }
389
390
391 //读取日志信息
392 private void readJournal() throws IOException {
393 InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);
394 try {
395 String magic = readAsciiLine(in);
396 String version = readAsciiLine(in);
397 String appVersionString = readAsciiLine(in);
398 String valueCountString = readAsciiLine(in);
399 String blank = readAsciiLine(in);
400 if (!MAGIC.equals(magic)
401 || !VERSION_1.equals(version)
402 || !Integer.toString(appVersion).equals(appVersionString)
403 || !Integer.toString(valueCount).equals(valueCountString)
404 || !"".equals(blank)) {
405 throw new IOException("unexpected journal header: ["
406 + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
407 }
408
409
410 while (true) {
411 try {
412 readJournalLine(readAsciiLine(in));//读取日志信息
413 } catch (EOFException endOfJournal) {
414 break;
415 }
416 }
417 } finally {
418 closeQuietly(in);//关闭输入流
419 }
420 }
421
422
423 //读取日志中某行日志信息
424 private void readJournalLine(String line) throws IOException {
425 String[] parts = line.split(" ");
426 if (parts.length < 2) {
427 throw new IOException("unexpected journal line: " + line);
428 }
429
430
431 String key = parts[1];
432 if (parts[0].equals(REMOVE) && parts.length == 2) {
433 lruEntries.remove(key);
434 return;
435 }
436
437
438 Entry entry = lruEntries.get(key);
439 if (entry == null) {
440 entry = new Entry(key);
441 lruEntries.put(key, entry);
442 }
443
444
445 if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
446 entry.readable = true;
447 entry.currentEditor = null;
448 entry.setLengths(copyOfRange(parts, 2, parts.length));
449 } else if (parts[0].equals(DIRTY) && parts.length == 2) {
450 entry.currentEditor = new Editor(entry);
451 } else if (parts[0].equals(READ) && parts.length == 2) {
452 // this work was already done by calling lruEntries.get()
453 } else {
454 throw new IOException("unexpected journal line: " + line);
455 }
456 }
457
458
459 /**
460 * Computes the initial size and collects garbage as a part of opening the
461 * cache. Dirty entries are assumed to be inconsistent and will be deleted.
462 * 处理日志
463 * 计算初始化cache的初始化大小和收集垃圾。Dirty entry假定不一致将会被删掉。
464 */
465 private void processJournal() throws IOException {
466 deleteIfExists(journalFileTmp);//删除日志文件
467 for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
468 Entry entry = i.next();
469 if (entry.currentEditor == null) {
470 for (int t = 0; t < valueCount; t++) {
471 size += entry.lengths[t];
472 }
473 } else {
474 entry.currentEditor = null;
475 for (int t = 0; t < valueCount; t++) {
476 deleteIfExists(entry.getCleanFile(t));
477 deleteIfExists(entry.getDirtyFile(t));
478 }
479 i.remove();
480 }
481 }
482 }
483
484
485 /**
486 * Creates a new journal that omits redundant information. This replaces the
487 * current journal if it exists.
488 * 创建一个新的删掉冗余信息的日志。替换当前的日志
489 */
490 private synchronized void rebuildJournal() throws IOException {
491 if (journalWriter != null) {
492 journalWriter.close();
493 }
494
495
496 Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
497 writer.write(MAGIC);
498 writer.write("\n");
499 writer.write(VERSION_1);
500 writer.write("\n");
501 writer.write(Integer.toString(appVersion));
502 writer.write("\n");
503 writer.write(Integer.toString(valueCount));
504 writer.write("\n");
505 writer.write("\n");
506
507
508 for (Entry entry : lruEntries.values()) {
509 if (entry.currentEditor != null) {
510 writer.write(DIRTY + ' ' + entry.key + '\n');
511 } else {
512 writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
513 }
514 }
515
516
517 writer.close();
518 journalFileTmp.renameTo(journalFile);
519 journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
520 }
521
522
523 //文件若存在删除
524 private static void deleteIfExists(File file) throws IOException {
525 // try {
526 // Libcore.os.remove(file.getPath());
527 // } catch (ErrnoException errnoException) {
528 // if (errnoException.errno != OsConstants.ENOENT) {
529 // throw errnoException.rethrowAsIOException();
530 // }
531 // }
532 if (file.exists() && !file.delete()) {
533 throw new IOException();
534 }
535 }
536
537
538 /**
539 * Returns a snapshot of the entry named {@code key}, or null if it doesn't
540 * exist is not currently readable. If a value is returned, it is moved to
541 * the head of the LRU queue.
542 * 返回key对应的entry的snapshot,当key相应的entry不存在或者当前不可读时返回null。
543 * 如果返回相应的值,它就会被移动到LRU队列的头部。
544 */
545 public synchronized Snapshot get(String key) throws IOException {
546 checkNotClosed();//检查cache是否已关闭
547 validateKey(key);//验证key格式的正确性
548 Entry entry = lruEntries.get(key);
549 if (entry == null) {
550 return null;
551 }
552
553
554 if (!entry.readable) {
555 return null;
556 }
557
558
559 /*
560 * Open all streams eagerly to guarantee that we see a single published
561 * snapshot. If we opened streams lazily then the streams could come
562 * from different edits.
563 */
564 InputStream[] ins = new InputStream[valueCount];
565 try {
566 for (int i = 0; i < valueCount; i++) {
567 ins[i] = new FileInputStream(entry.getCleanFile(i));
568 }
569 } catch (FileNotFoundException e) {
570 // a file must have been deleted manually!
571 return null;
572 }
573
574
575 redundantOpCount++;
576 journalWriter.append(READ + ' ' + key + '\n');
577 if (journalRebuildRequired()) {
578 executorService.submit(cleanupCallable);
579 }
580
581
582 return new Snapshot(key, entry.sequenceNumber, ins);
583 }
584
585
586 /**
587 * Returns an editor for the entry named {@code key}, or null if another
588 * edit is in progress.
589 */
590 public Editor edit(String key) throws IOException {
591 return edit(key, ANY_SEQUENCE_NUMBER);
592 }
593
594
595 //有key和序列号生成一个editor
596 private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
597 checkNotClosed();//检查cache关闭与否
598 validateKey(key);//验证key格式正确性
599 Entry entry = lruEntries.get(key);
600 if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
601 && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
602 return null; // snapshot is stale
603 }
604 if (entry == null) {
605 entry = new Entry(key);
606 lruEntries.put(key, entry);
607 } else if (entry.currentEditor != null) {
608 return null; // another edit is in progress
609 }
610
611
612 Editor editor = new Editor(entry);
613 entry.currentEditor = editor;
614
615
616 // flush the journal before creating files to prevent file leaks
617 journalWriter.write(DIRTY + ' ' + key + '\n');
618 journalWriter.flush();
619 return editor;
620 }
621
622
623 /**
624 * Returns the directory where this cache stores its data.
625 */
626 public File getDirectory() {
627 return directory;
628 }
629
630
631 /**
632 * Returns the maximum number of bytes that this cache should use to store
633 * its data.
634 */
635 public long maxSize() {
636 return maxSize;
637 }
638
639
640 /**
641 * Returns the number of bytes currently being used to store the values in
642 * this cache. This may be greater than the max size if a background
643 * deletion is pending.
644 */
645 public synchronized long size() {
646 return size;
647 }
648
649
650 //完成Edit动作
651 private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
652 Entry entry = editor.entry;
653 if (entry.currentEditor != editor) {
654 throw new IllegalStateException();
655 }
656
657
658 // if this edit is creating the entry for the first time, every index must have a value
659 if (success && !entry.readable) {
660 for (int i = 0; i < valueCount; i++) {
661 if (!entry.getDirtyFile(i).exists()) {
662 editor.abort();
663 throw new IllegalStateException("edit didn't create file " + i);
664 }
665 }
666 }
667
668
669 for (int i = 0; i < valueCount; i++) {
670 File dirty = entry.getDirtyFile(i);
671 if (success) {
672 if (dirty.exists()) {
673 File clean = entry.getCleanFile(i);
674 dirty.renameTo(clean);
675 long oldLength = entry.lengths[i];
676 long newLength = clean.length();
677 entry.lengths[i] = newLength;
678 size = size - oldLength + newLength;
679 }
680 } else {
681 deleteIfExists(dirty);
682 }
683 }
684
685
686 redundantOpCount++;
687 entry.currentEditor = null;
688 if (entry.readable | success) {
689 entry.readable = true;
690 journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
691 if (success) {
692 entry.sequenceNumber = nextSequenceNumber++;
693 }
694 } else {
695 lruEntries.remove(entry.key);
696 journalWriter.write(REMOVE + ' ' + entry.key + '\n');
697 }
698
699
700 if (size > maxSize || journalRebuildRequired()) {
701 executorService.submit(cleanupCallable);
702 }
703 }
704
705
706 /**
707 * We only rebuild the journal when it will halve the size of the journal
708 * and eliminate at least 2000 ops.
709 * 当日志大小减半并且删掉至少2000项时重新构造日志
710 */
711 private boolean journalRebuildRequired() {
712 final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
713 return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
714 && redundantOpCount >= lruEntries.size();
715 }
716
717
718 /**
719 * Drops the entry for {@code key} if it exists and can be removed. Entries
720 * actively being edited cannot be removed.
721 * 删除key相应的entry,被编辑的Entry不能被remove
722 * @return true if an entry was removed.
723 */
724 public synchronized boolean remove(String key) throws IOException {
725 checkNotClosed();//检查cache是否已经关闭
726 validateKey(key);//验证key格式的正确性
727 Entry entry = lruEntries.get(key);
728 if (entry == null || entry.currentEditor != null) {
729 return false;
730 }
731
732
733 for (int i = 0; i < valueCount; i++) {
734 File file = entry.getCleanFile(i);
735 if (!file.delete()) {
736 throw new IOException("failed to delete " + file);
737 }
738 size -= entry.lengths[i];
739 entry.lengths[i] = 0;
740 }
741
742
743 redundantOpCount++;
744 journalWriter.append(REMOVE + ' ' + key + '\n');
745 lruEntries.remove(key);
746
747
748 if (journalRebuildRequired()) {
749 executorService.submit(cleanupCallable);
750 }
751
752
753 return true;
754 }
755
756
757 /**
758 * Returns true if this cache has been closed.
759 * 判断cache是否已经关闭
760 */
761 public boolean isClosed() {
762 return journalWriter == null;
763 }
764
765
766 //检查cache是否已经关闭
767 private void checkNotClosed() {
768 if (journalWriter == null) {
769 throw new IllegalStateException("cache is closed");
770 }
771 }
772
773
774 /**
775 * Force buffered operations to the filesystem.
776 */
777 public synchronized void flush() throws IOException {
778 checkNotClosed();//检查cache是否关闭
779 trimToSize();//满足最大空间limit
780 journalWriter.flush();
781 }
782
783
784 /**
785 * Closes this cache. Stored values will remain on the filesystem.
786 * 关闭cache。
787 */
788 public synchronized void close() throws IOException {
789 if (journalWriter == null) {
790 return; // already closed
791 }
792 for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
793 if (entry.currentEditor != null) {
794 entry.currentEditor.abort();
795 }
796 }
797 trimToSize();
798 journalWriter.close();
799 journalWriter = null;
800 }
801
802
803 //回收删除某些entry到空间大小满足maxsize
804 private void trimToSize() throws IOException {
805 while (size > maxSize) {
806 // Map.Entry<String, Entry> toEvict = lruEntries.eldest();
807 final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
808 remove(toEvict.getKey());
809 }
810 }
811
812
813 /**
814 * Closes the cache and deletes all of its stored values. This will delete
815 * all files in the cache directory including files that weren't created by
816 * the cache.
817 * 关闭删除cache
818 */
819 public void delete() throws IOException {
820 close();
821 deleteContents(directory);
822 }
823
824
825 //验证key格式的正确性
826 private void validateKey(String key) {
827 if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
828 throw new IllegalArgumentException(
829 "keys must not contain spaces or newlines: \"" + key + "\"");
830 }
831 }
832
833
834 //字符串形式读出输入流的内容
835 private static String inputStreamToString(InputStream in) throws IOException {
836 return readFully(new InputStreamReader(in, UTF_8));
837 }
838
839
840 /**
841 * A snapshot of the values for an entry.
842 * entry的快照
843 */
844 public final class Snapshot implements Closeable {
845 private final String key;//key
846 private final long sequenceNumber;//序列号(同文件名称)
847 private final InputStream[] ins;//两个修改的文件输入流
848
849
850 private Snapshot(String key, long sequenceNumber, InputStream[] ins) {
851 this.key = key;
852 this.sequenceNumber = sequenceNumber;
853 this.ins = ins;
854 }
855
856
857 /**
858 * Returns an editor for this snapshot's entry, or null if either the
859 * entry has changed since this snapshot was created or if another edit
860 * is in progress.
861 * 返回entry快照的editor,如果entry已经更新了或者另一个edit正在处理过程中返回null。
862 */
863 public Editor edit() throws IOException {
864 return DiskLruCache.this.edit(key, sequenceNumber);
865 }
866
867
868 /**
869 * Returns the unbuffered stream with the value for {@code index}.
870 */
871 public InputStream getInputStream(int index) {
872 return ins[index];
873 }
874
875
876 /**
877 * Returns the string value for {@code index}.
878 */
879 public String getString(int index) throws IOException {
880 return inputStreamToString(getInputStream(index));
881 }
882
883
884 @Override public void close() {
885 for (InputStream in : ins) {
886 closeQuietly(in);
887 }
888 }
889 }
890
891
892 /**
893 * Edits the values for an entry.
894 * entry编辑器
895 */
896 public final class Editor {
897 private final Entry entry;
898 private boolean hasErrors;
899
900
901 private Editor(Entry entry) {
902 this.entry = entry;
903 }
904
905
906 /**
907 * Returns an unbuffered input stream to read the last committed value,
908 * or null if no value has been committed.
909 * 返回一个最后提交的entry的不缓存输入流,如果没有值被提交过返回null
910 */
911 public InputStream newInputStream(int index) throws IOException {
912 synchronized (DiskLruCache.this) {
913 if (entry.currentEditor != this) {
914 throw new IllegalStateException();
915 }
916 if (!entry.readable) {
917 return null;
918 }
919 return new FileInputStream(entry.getCleanFile(index));
920 }
921 }
922
923
924 /**
925 * Returns the last committed value as a string, or null if no value
926 * has been committed.
927 * 返回最后提交的entry的文件内容,字符串形式
928 */
929 public String getString(int index) throws IOException {
930 InputStream in = newInputStream(index);
931 return in != null ? inputStreamToString(in) : null;
932 }
933
934
935 /**
936 * Returns a new unbuffered output stream to write the value at
937 * {@code index}. If the underlying output stream encounters errors
938 * when writing to the filesystem, this edit will be aborted when
939 * {@link #commit} is called. The returned output stream does not throw
940 * IOExceptions.
941 * 返回一个新的无缓冲的输出流,写文件时如果潜在的输出流存在错误,这个edit将被废弃。
942 */
943 public OutputStream newOutputStream(int index) throws IOException {
944 synchronized (DiskLruCache.this) {
945 if (entry.currentEditor != this) {
946 throw new IllegalStateException();
947 }
948 return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
949 }
950 }
951
952
953 /**
954 * Sets the value at {@code index} to {@code value}.
955 * 设置entry的value的文件的内容
956 */
957 public void set(int index, String value) throws IOException {
958 Writer writer = null;
959 try {
960 writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
961 writer.write(value);
962 } finally {
963 closeQuietly(writer);
964 }
965 }
966
967
968 /**
969 * Commits this edit so it is visible to readers. This releases the
970 * edit lock so another edit may be started on the same key.
971 * commit提交编辑的结果,释放edit锁然后其它edit可以启动
972 */
973 public void commit() throws IOException {
974 if (hasErrors) {
975 completeEdit(this, false);
976 remove(entry.key); // the previous entry is stale
977 } else {
978 completeEdit(this, true);
979 }
980 }
981
982
983 /**
984 * Aborts this edit. This releases the edit lock so another edit may be
985 * started on the same key.
986 * 废弃edit,释放edit锁然后其它edit可以启动
987 */
988 public void abort() throws IOException {
989 completeEdit(this, false);
990 }
991
992
993 //包装的输出流类
994 private class FaultHidingOutputStream extends FilterOutputStream {
995 private FaultHidingOutputStream(OutputStream out) {
996 super(out);
997 }
998
999
1000 @Override public void write(int oneByte) {
1001 try {
1002 out.write(oneByte);
1003 } catch (IOException e) {
1004 hasErrors = true;
1005 }
1006 }
1007
1008
1009 @Override public void write(byte[] buffer, int offset, int length) {
1010 try {
1011 out.write(buffer, offset, length);
1012 } catch (IOException e) {
1013 hasErrors = true;
1014 }
1015 }
1016
1017
1018 @Override public void close() {
1019 try {
1020 out.close();
1021 } catch (IOException e) {
1022 hasErrors = true;
1023 }
1024 }
1025
1026
1027 @Override public void flush() {
1028 try {
1029 out.flush();
1030 } catch (IOException e) {
1031 hasErrors = true;
1032 }
1033 }
1034 }
1035 }
1036
1037
1038 /*
1039 * Entry 最终类
1040 */
1041 private final class Entry {
1042 private final String key;
1043
1044
1045 /** Lengths of this entry's files. */
1046 private final long[] lengths;//每一个cache文件的长度
1047
1048
1049 /** True if this entry has ever been published */
1050 private boolean readable;
1051
1052
1053 /** The ongoing edit or null if this entry is not being edited. */
1054 private Editor currentEditor;
1055
1056
1057 /** The sequence number of the most recently committed edit to this entry. */
1058 private long sequenceNumber;
1059
1060
1061 private Entry(String key) {
1062 this.key = key;
1063 this.lengths = new long[valueCount];
1064 }
1065
1066
1067 public String getLengths() throws IOException {
1068 StringBuilder result = new StringBuilder();
1069 for (long size : lengths) {
1070 result.append(' ').append(size);
1071 }
1072 return result.toString();
1073 }
1074
1075
1076 /**
1077 * Set lengths using decimal numbers like "10123".
1078 * 设置每一个cache文件的长度(即lengths[i]的长度)
1079 */
1080 private void setLengths(String[] strings) throws IOException {
1081 if (strings.length != valueCount) {
1082 throw invalidLengths(strings);
1083 }
1084
1085
1086 try {
1087 for (int i = 0; i < strings.length; i++) {
1088 lengths[i] = Long.parseLong(strings[i]);
1089 }
1090 } catch (NumberFormatException e) {
1091 throw invalidLengths(strings);
1092 }
1093 }
1094
1095
1096 private IOException invalidLengths(String[] strings) throws IOException {
1097 throw new IOException("unexpected journal line: " + Arrays.toString(strings));
1098 }
1099
1100
1101 public File getCleanFile(int i) {
1102 return new File(directory, key + "." + i);
1103 }
1104
1105
1106 public File getDirtyFile(int i) {
1107 return new File(directory, key + "." + i + ".tmp");
1108 }
1109 }
1110 }