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 }
转自http://blog.youkuaiyun.com/linghu_java/article/details/8600053