Android4.1.1中的LruDiskCache源码

  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. package libcore.io;

  17. import java.io.BufferedInputStream;
  18. import java.io.BufferedWriter;
  19. import java.io.Closeable;
  20. import java.io.EOFException;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileNotFoundException;
  24. import java.io.FileOutputStream;
  25. import java.io.FileWriter;
  26. import java.io.FilterOutputStream;
  27. import java.io.IOException;
  28. import java.io.InputStream;
  29. import java.io.InputStreamReader;
  30. import java.io.OutputStream;
  31. import java.io.OutputStreamWriter;
  32. import java.io.Writer;
  33. import java.nio.charset.Charsets;
  34. import java.util.ArrayList;
  35. import java.util.Arrays;
  36. import java.util.Iterator;
  37. import java.util.LinkedHashMap;
  38. import java.util.Map;
  39. import java.util.concurrent.Callable;
  40. import java.util.concurrent.ExecutorService;
  41. import java.util.concurrent.LinkedBlockingQueue;
  42. import java.util.concurrent.ThreadPoolExecutor;
  43. import java.util.concurrent.TimeUnit;

  44. /**
  45.  * A cache that uses a bounded amount of space on a filesystem. Each cache
  46.  * entry has a string key and a fixed number of values. Values are byte
  47.  * sequences, accessible as streams or files. Each value must be between {@code
  48.  * 0} and {@code Integer.MAX_VALUE} bytes in length.
  49.  *
  50.  * <p>The cache stores its data in a directory on the filesystem. This
  51.  * directory must be exclusive to the cache; the cache may delete or overwrite
  52.  * files from its directory. It is an error for multiple processes to use the
  53.  * same cache directory at the same time.
  54.  *
  55.  * <p>This cache limits the number of bytes that it will store on the
  56.  * filesystem. When the number of stored bytes exceeds the limit, the cache will
  57.  * remove entries in the background until the limit is satisfied. The limit is
  58.  * not strict: the cache may temporarily exceed it while waiting for files to be
  59.  * deleted. The limit does not include filesystem overhead or the cache
  60.  * journal so space-sensitive applications should set a conservative limit.
  61.  *
  62.  * <p>Clients call {@link #edit} to create or update the values of an entry. An
  63.  * entry may have only one editor at one time; if a value is not available to be
  64.  * edited then {@link #edit} will return null.
  65.  * <ul>
  66.  *     <li>When an entry is being <strong>created</strong> it is necessary to
  67.  *         supply a full set of values; the empty value should be used as a
  68.  *         placeholder if necessary.
  69.  *     <li>When an entry is being <strong>edited</strong>, it is not necessary
  70.  *         to supply data for every value; values default to their previous
  71.  *         value.
  72.  * </ul>
  73.  * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
  74.  * or {@link Editor#abort}. Committing is atomic: a read observes the full set
  75.  * of values as they were before or after the commit, but never a mix of values.
  76.  *
  77.  * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
  78.  * observe the value at the time that {@link #get} was called. Updates and
  79.  * removals after the call do not impact ongoing reads.
  80.  *
  81.  * <p>This class is tolerant of some I/O errors. If files are missing from the
  82.  * filesystem, the corresponding entries will be dropped from the cache. If
  83.  * an error occurs while writing a cache value, the edit will fail silently.
  84.  * Callers should handle other problems by catching {@code IOException} and
  85.  * responding appropriately.
  86.  */
  87. public final class DiskLruCache implements Closeable {
  88.     static final String JOURNAL_FILE = "journal";
  89.     static final String JOURNAL_FILE_TMP = "journal.tmp";
  90.     static final String MAGIC = "libcore.io.DiskLruCache";
  91.     static final String VERSION_1 = "1";
  92.     static final long ANY_SEQUENCE_NUMBER = -1;
  93.     private static final String CLEAN = "CLEAN";
  94.     private static final String DIRTY = "DIRTY";
  95.     private static final String REMOVE = "REMOVE";
  96.     private static final String READ = "READ";

  97.     /*
  98.      * This cache uses a journal file named "journal". A typical journal file
  99.      * looks like this:
  100.      *     libcore.io.DiskLruCache
  101.      *     1
  102.      *     100
  103.      *     2
  104.      *
  105.      *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
  106.      *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
  107.      *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
  108.      *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
  109.      *     DIRTY 1ab96a171faeeee38496d8b330771a7a
  110.      *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
  111.      *     READ 335c4c6028171cfddfbaae1a9c313c52
  112.      *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
  113.      *
  114.      * The first five lines of the journal form its header. They are the
  115.      * constant string "libcore.io.DiskLruCache", the disk cache's version,
  116.      * the application's version, the value count, and a blank line.
  117.      *
  118.      * Each of the subsequent lines in the file is a record of the state of a
  119.      * cache entry. Each line contains space-separated values: a state, a key,
  120.      * and optional state-specific values.
  121.      *   o DIRTY lines track that an entry is actively being created or updated.
  122.      *     Every successful DIRTY action should be followed by a CLEAN or REMOVE
  123.      *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that
  124.      *     temporary files may need to be deleted.
  125.      *   o CLEAN lines track a cache entry that has been successfully published
  126.      *     and may be read. A publish line is followed by the lengths of each of
  127.      *     its values.
  128.      *   o READ lines track accesses for LRU.
  129.      *   o REMOVE lines track entries that have been deleted.
  130.      *
  131.      * The journal file is appended to as cache operations occur. The journal may
  132.      * occasionally be compacted by dropping redundant lines. A temporary file named
  133.      * "journal.tmp" will be used during compaction; that file should be deleted if
  134.      * it exists when the cache is opened.
  135.      */

  136.     private final File directory;
  137.     private final File journalFile;
  138.     private final File journalFileTmp;
  139.     private final int appVersion;
  140.     private final long maxSize;
  141.     private final int valueCount;
  142.     private long size = 0;
  143.     private Writer journalWriter;
  144.     private final LinkedHashMap<String, Entry> lruEntries
  145.             = new LinkedHashMap<String, Entry>(0, 0.75f, true);
  146.     private int redundantOpCount;

  147.     /**
  148.      * To differentiate between old and current snapshots, each entry is given
  149.      * a sequence number each time an edit is committed. A snapshot is stale if
  150.      * its sequence number is not equal to its entry's sequence number.
  151.      */
  152.     private long nextSequenceNumber = 0;

  153.     /** This cache uses a single background thread to evict entries. */
  154.     private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
  155.             60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
  156.     private final Callable<Void> cleanupCallable = new Callable<Void>() {
  157.         @Override public Void call() throws Exception {
  158.             synchronized (DiskLruCache.this) {
  159.                 if (journalWriter == null) {
  160.                     return null; // closed
  161.                 }
  162.                 trimToSize();
  163.                 if (journalRebuildRequired()) {
  164.                     rebuildJournal();
  165.                     redundantOpCount = 0;
  166.                 }
  167.             }
  168.             return null;
  169.         }
  170.     };

  171.     private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
  172.         this.directory = directory;
  173.         this.appVersion = appVersion;
  174.         this.journalFile = new File(directory, JOURNAL_FILE);
  175.         this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
  176.         this.valueCount = valueCount;
  177.         this.maxSize = maxSize;
  178.     }

  179.     /**
  180.      * Opens the cache in {@code directory}, creating a cache if none exists
  181.      * there.
  182.      *
  183.      * @param directory a writable directory
  184.      * @param appVersion
  185.      * @param valueCount the number of values per cache entry. Must be positive.
  186.      * @param maxSize the maximum number of bytes this cache should use to store
  187.      * @throws IOException if reading or writing the cache directory fails
  188.      */
  189.     public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
  190.             throws IOException {
  191.         if (maxSize <= 0) {
  192.             throw new IllegalArgumentException("maxSize <= 0");
  193.         }
  194.         if (valueCount <= 0) {
  195.             throw new IllegalArgumentException("valueCount <= 0");
  196.         }

  197.         // prefer to pick up where we left off
  198.         DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
  199.         if (cache.journalFile.exists()) {
  200.             try {
  201.                 cache.readJournal();
  202.                 cache.processJournal();
  203.                 cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true));
  204.                 return cache;
  205.             } catch (IOException journalIsCorrupt) {
  206.                 System.logW("DiskLruCache " + directory + " is corrupt: "
  207.                         + journalIsCorrupt.getMessage() + ", removing");
  208.                 cache.delete();
  209.             }
  210.         }

  211.         // create a new empty cache
  212.         directory.mkdirs();
  213.         cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
  214.         cache.rebuildJournal();
  215.         return cache;
  216.     }

  217.     private void readJournal() throws IOException {
  218.         InputStream in = new BufferedInputStream(new FileInputStream(journalFile));
  219.         try {
  220.             String magic = Streams.readAsciiLine(in);
  221.             String version = Streams.readAsciiLine(in);
  222.             String appVersionString = Streams.readAsciiLine(in);
  223.             String valueCountString = Streams.readAsciiLine(in);
  224.             String blank = Streams.readAsciiLine(in);
  225.             if (!MAGIC.equals(magic)
  226.                     || !VERSION_1.equals(version)
  227.                     || !Integer.toString(appVersion).equals(appVersionString)
  228.                     || !Integer.toString(valueCount).equals(valueCountString)
  229.                     || !"".equals(blank)) {
  230.                 throw new IOException("unexpected journal header: ["
  231.                         + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
  232.             }

  233.             while (true) {
  234.                 try {
  235.                     readJournalLine(Streams.readAsciiLine(in));
  236.                 } catch (EOFException endOfJournal) {
  237.                     break;
  238.                 }
  239.             }
  240.         } finally {
  241.             IoUtils.closeQuietly(in);
  242.         }
  243.     }

  244.     private void readJournalLine(String line) throws IOException {
  245.         String[] parts = line.split(" ");
  246.         if (parts.length < 2) {
  247.             throw new IOException("unexpected journal line: " + line);
  248.         }

  249.         String key = parts[1];
  250.         if (parts[0].equals(REMOVE) && parts.length == 2) {
  251.             lruEntries.remove(key);
  252.             return;
  253.         }

  254.         Entry entry = lruEntries.get(key);
  255.         if (entry == null) {
  256.             entry = new Entry(key);
  257.             lruEntries.put(key, entry);
  258.         }

  259.         if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
  260.             entry.readable = true;
  261.             entry.currentEditor = null;
  262.             entry.setLengths(Arrays.copyOfRange(parts, 2, parts.length));
  263.         } else if (parts[0].equals(DIRTY) && parts.length == 2) {
  264.             entry.currentEditor = new Editor(entry);
  265.         } else if (parts[0].equals(READ) && parts.length == 2) {
  266.             // this work was already done by calling lruEntries.get()
  267.         } else {
  268.             throw new IOException("unexpected journal line: " + line);
  269.         }
  270.     }

  271.     /**
  272.      * Computes the initial size and collects garbage as a part of opening the
  273.      * cache. Dirty entries are assumed to be inconsistent and will be deleted.
  274.      */
  275.     private void processJournal() throws IOException {
  276.         deleteIfExists(journalFileTmp);
  277.         for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
  278.             Entry entry = i.next();
  279.             if (entry.currentEditor == null) {
  280.                 for (int t = 0; t < valueCount; t++) {
  281.                     size += entry.lengths[t];
  282.                 }
  283.             } else {
  284.                 entry.currentEditor = null;
  285.                 for (int t = 0; t < valueCount; t++) {
  286.                     deleteIfExists(entry.getCleanFile(t));
  287.                     deleteIfExists(entry.getDirtyFile(t));
  288.                 }
  289.                 i.remove();
  290.             }
  291.         }
  292.     }

  293.     /**
  294.      * Creates a new journal that omits redundant information. This replaces the
  295.      * current journal if it exists.
  296.      */
  297.     private synchronized void rebuildJournal() throws IOException {
  298.         if (journalWriter != null) {
  299.             journalWriter.close();
  300.         }

  301.         Writer writer = new BufferedWriter(new FileWriter(journalFileTmp));
  302.         writer.write(MAGIC);
  303.         writer.write("\n");
  304.         writer.write(VERSION_1);
  305.         writer.write("\n");
  306.         writer.write(Integer.toString(appVersion));
  307.         writer.write("\n");
  308.         writer.write(Integer.toString(valueCount));
  309.         writer.write("\n");
  310.         writer.write("\n");

  311.         for (Entry entry : lruEntries.values()) {
  312.             if (entry.currentEditor != null) {
  313.                 writer.write(DIRTY + ' ' + entry.key + '\n');
  314.             } else {
  315.                 writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
  316.             }
  317.         }

  318.         writer.close();
  319.         journalFileTmp.renameTo(journalFile);
  320.         journalWriter = new BufferedWriter(new FileWriter(journalFile, true));
  321.     }

  322.     private static void deleteIfExists(File file) throws IOException {
  323.         try {
  324.             Libcore.os.remove(file.getPath());
  325.         } catch (ErrnoException errnoException) {
  326.             if (errnoException.errno != OsConstants.ENOENT) {
  327.                 throw errnoException.rethrowAsIOException();
  328.             }
  329.         }
  330.     }

  331.     /**
  332.      * Returns a snapshot of the entry named {@code key}, or null if it doesn't
  333.      * exist is not currently readable. If a value is returned, it is moved to
  334.      * the head of the LRU queue.
  335.      */
  336.     public synchronized Snapshot get(String key) throws IOException {
  337.         checkNotClosed();
  338.         validateKey(key);
  339.         Entry entry = lruEntries.get(key);
  340.         if (entry == null) {
  341.             return null;
  342.         }

  343.         if (!entry.readable) {
  344.             return null;
  345.         }

  346.         /*
  347.          * Open all streams eagerly to guarantee that we see a single published
  348.          * snapshot. If we opened streams lazily then the streams could come
  349.          * from different edits.
  350.          */
  351.         InputStream[] ins = new InputStream[valueCount];
  352.         try {
  353.             for (int i = 0; i < valueCount; i++) {
  354.                 ins[i] = new FileInputStream(entry.getCleanFile(i));
  355.             }
  356.         } catch (FileNotFoundException e) {
  357.             // a file must have been deleted manually!
  358.             return null;
  359.         }

  360.         redundantOpCount++;
  361.         journalWriter.append(READ + ' ' + key + '\n');
  362.         if (journalRebuildRequired()) {
  363.             executorService.submit(cleanupCallable);
  364.         }

  365.         return new Snapshot(key, entry.sequenceNumber, ins);
  366.     }

  367.     /**
  368.      * Returns an editor for the entry named {@code key}, or null if another
  369.      * edit is in progress.
  370.      */
  371.     public Editor edit(String key) throws IOException {
  372.         return edit(key, ANY_SEQUENCE_NUMBER);
  373.     }

  374.     private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
  375.         checkNotClosed();
  376.         validateKey(key);
  377.         Entry entry = lruEntries.get(key);
  378.         if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
  379.                 && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
  380.             return null; // snapshot is stale
  381.         }
  382.         if (entry == null) {
  383.             entry = new Entry(key);
  384.             lruEntries.put(key, entry);
  385.         } else if (entry.currentEditor != null) {
  386.             return null; // another edit is in progress
  387.         }

  388.         Editor editor = new Editor(entry);
  389.         entry.currentEditor = editor;

  390.         // flush the journal before creating files to prevent file leaks
  391.         journalWriter.write(DIRTY + ' ' + key + '\n');
  392.         journalWriter.flush();
  393.         return editor;
  394.     }

  395.     /**
  396.      * Returns the directory where this cache stores its data.
  397.      */
  398.     public File getDirectory() {
  399.         return directory;
  400.     }

  401.     /**
  402.      * Returns the maximum number of bytes that this cache should use to store
  403.      * its data.
  404.      */
  405.     public long maxSize() {
  406.         return maxSize;
  407.     }

  408.     /**
  409.      * Returns the number of bytes currently being used to store the values in
  410.      * this cache. This may be greater than the max size if a background
  411.      * deletion is pending.
  412.      */
  413.     public synchronized long size() {
  414.         return size;
  415.     }

  416.     private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
  417.         Entry entry = editor.entry;
  418.         if (entry.currentEditor != editor) {
  419.             throw new IllegalStateException();
  420.         }

  421.         // if this edit is creating the entry for the first time, every index must have a value
  422.         if (success && !entry.readable) {
  423.             for (int i = 0; i < valueCount; i++) {
  424.                 if (!entry.getDirtyFile(i).exists()) {
  425.                     editor.abort();
  426.                     throw new IllegalStateException("edit didn't create file " + i);
  427.                 }
  428.             }
  429.         }

  430.         for (int i = 0; i < valueCount; i++) {
  431.             File dirty = entry.getDirtyFile(i);
  432.             if (success) {
  433.                 if (dirty.exists()) {
  434.                     File clean = entry.getCleanFile(i);
  435.                     dirty.renameTo(clean);
  436.                     long oldLength = entry.lengths[i];
  437.                     long newLength = clean.length();
  438.                     entry.lengths[i] = newLength;
  439.                     size = size - oldLength + newLength;
  440.                 }
  441.             } else {
  442.                 deleteIfExists(dirty);
  443.             }
  444.         }

  445.         redundantOpCount++;
  446.         entry.currentEditor = null;
  447.         if (entry.readable | success) {
  448.             entry.readable = true;
  449.             journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
  450.             if (success) {
  451.                 entry.sequenceNumber = nextSequenceNumber++;
  452.             }
  453.         } else {
  454.             lruEntries.remove(entry.key);
  455.             journalWriter.write(REMOVE + ' ' + entry.key + '\n');
  456.         }

  457.         if (size > maxSize || journalRebuildRequired()) {
  458.             executorService.submit(cleanupCallable);
  459.         }
  460.     }

  461.     /**
  462.      * We only rebuild the journal when it will halve the size of the journal
  463.      * and eliminate at least 2000 ops.
  464.      */
  465.     private boolean journalRebuildRequired() {
  466.         final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
  467.         return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
  468.                 && redundantOpCount >= lruEntries.size();
  469.     }

  470.     /**
  471.      * Drops the entry for {@code key} if it exists and can be removed. Entries
  472.      * actively being edited cannot be removed.
  473.      *
  474.      * @return true if an entry was removed.
  475.      */
  476.     public synchronized boolean remove(String key) throws IOException {
  477.         checkNotClosed();
  478.         validateKey(key);
  479.         Entry entry = lruEntries.get(key);
  480.         if (entry == null || entry.currentEditor != null) {
  481.             return false;
  482.         }

  483.         for (int i = 0; i < valueCount; i++) {
  484.             File file = entry.getCleanFile(i);
  485.             if (!file.delete()) {
  486.                 throw new IOException("failed to delete " + file);
  487.             }
  488.             size -= entry.lengths[i];
  489.             entry.lengths[i] = 0;
  490.         }

  491.         redundantOpCount++;
  492.         journalWriter.append(REMOVE + ' ' + key + '\n');
  493.         lruEntries.remove(key);

  494.         if (journalRebuildRequired()) {
  495.             executorService.submit(cleanupCallable);
  496.         }

  497.         return true;
  498.     }

  499.     /**
  500.      * Returns true if this cache has been closed.
  501.      */
  502.     public boolean isClosed() {
  503.         return journalWriter == null;
  504.     }

  505.     private void checkNotClosed() {
  506.         if (journalWriter == null) {
  507.             throw new IllegalStateException("cache is closed");
  508.         }
  509.     }

  510.     /**
  511.      * Force buffered operations to the filesystem.
  512.      */
  513.     public synchronized void flush() throws IOException {
  514.         checkNotClosed();
  515.         trimToSize();
  516.         journalWriter.flush();
  517.     }

  518.     /**
  519.      * Closes this cache. Stored values will remain on the filesystem.
  520.      */
  521.     public synchronized void close() throws IOException {
  522.         if (journalWriter == null) {
  523.             return; // already closed
  524.         }
  525.         for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
  526.             if (entry.currentEditor != null) {
  527.                 entry.currentEditor.abort();
  528.             }
  529.         }
  530.         trimToSize();
  531.         journalWriter.close();
  532.         journalWriter = null;
  533.     }

  534.     private void trimToSize() throws IOException {
  535.         while (size > maxSize) {
  536.             Map.Entry<String, Entry> toEvict = lruEntries.eldest();
  537.             remove(toEvict.getKey());
  538.         }
  539.     }

  540.     /**
  541.      * Closes the cache and deletes all of its stored values. This will delete
  542.      * all files in the cache directory including files that weren't created by
  543.      * the cache.
  544.      */
  545.     public void delete() throws IOException {
  546.         close();
  547.         IoUtils.deleteContents(directory);
  548.     }

  549.     private void validateKey(String key) {
  550.         if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
  551.             throw new IllegalArgumentException(
  552.                     "keys must not contain spaces or newlines: \"" + key + "\"");
  553.         }
  554.     }

  555.     private static String inputStreamToString(InputStream in) throws IOException {
  556.         return Streams.readFully(new InputStreamReader(in, Charsets.UTF_8));
  557.     }

  558.     /**
  559.      * A snapshot of the values for an entry.
  560.      */
  561.     public final class Snapshot implements Closeable {
  562.         private final String key;
  563.         private final long sequenceNumber;
  564.         private final InputStream[] ins;

  565.         private Snapshot(String key, long sequenceNumber, InputStream[] ins) {
  566.             this.key = key;
  567.             this.sequenceNumber = sequenceNumber;
  568.             this.ins = ins;
  569.         }

  570.         /**
  571.          * Returns an editor for this snapshot's entry, or null if either the
  572.          * entry has changed since this snapshot was created or if another edit
  573.          * is in progress.
  574.          */
  575.         public Editor edit() throws IOException {
  576.             return DiskLruCache.this.edit(key, sequenceNumber);
  577.         }

  578.         /**
  579.          * Returns the unbuffered stream with the value for {@code index}.
  580.          */
  581.         public InputStream getInputStream(int index) {
  582.             return ins[index];
  583.         }

  584.         /**
  585.          * Returns the string value for {@code index}.
  586.          */
  587.         public String getString(int index) throws IOException {
  588.             return inputStreamToString(getInputStream(index));
  589.         }

  590.         @Override public void close() {
  591.             for (InputStream in : ins) {
  592.                 IoUtils.closeQuietly(in);
  593.             }
  594.         }
  595.     }

  596.     /**
  597.      * Edits the values for an entry.
  598.      */
  599.     public final class Editor {
  600.         private final Entry entry;
  601.         private boolean hasErrors;

  602.         private Editor(Entry entry) {
  603.             this.entry = entry;
  604.         }

  605.         /**
  606.          * Returns an unbuffered input stream to read the last committed value,
  607.          * or null if no value has been committed.
  608.          */
  609.         public InputStream newInputStream(int index) throws IOException {
  610.             synchronized (DiskLruCache.this) {
  611.                 if (entry.currentEditor != this) {
  612.                     throw new IllegalStateException();
  613.                 }
  614.                 if (!entry.readable) {
  615.                     return null;
  616.                 }
  617.                 return new FileInputStream(entry.getCleanFile(index));
  618.             }
  619.         }

  620.         /**
  621.          * Returns the last committed value as a string, or null if no value
  622.          * has been committed.
  623.          */
  624.         public String getString(int index) throws IOException {
  625.             InputStream in = newInputStream(index);
  626.             return in != null ? inputStreamToString(in) : null;
  627.         }

  628.         /**
  629.          * Returns a new unbuffered output stream to write the value at
  630.          * {@code index}. If the underlying output stream encounters errors
  631.          * when writing to the filesystem, this edit will be aborted when
  632.          * {@link #commit} is called. The returned output stream does not throw
  633.          * IOExceptions.
  634.          */
  635.         public OutputStream newOutputStream(int index) throws IOException {
  636.             synchronized (DiskLruCache.this) {
  637.                 if (entry.currentEditor != this) {
  638.                     throw new IllegalStateException();
  639.                 }
  640.                 return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
  641.             }
  642.         }

  643.         /**
  644.          * Sets the value at {@code index} to {@code value}.
  645.          */
  646.         public void set(int index, String value) throws IOException {
  647.             Writer writer = null;
  648.             try {
  649.                 writer = new OutputStreamWriter(newOutputStream(index), Charsets.UTF_8);
  650.                 writer.write(value);
  651.             } finally {
  652.                 IoUtils.closeQuietly(writer);
  653.             }
  654.         }

  655.         /**
  656.          * Commits this edit so it is visible to readers.  This releases the
  657.          * edit lock so another edit may be started on the same key.
  658.          */
  659.         public void commit() throws IOException {
  660.             if (hasErrors) {
  661.                 completeEdit(this, false);
  662.                 remove(entry.key); // the previous entry is stale
  663.             } else {
  664.                 completeEdit(this, true);
  665.             }
  666.         }

  667.         /**
  668.          * Aborts this edit. This releases the edit lock so another edit may be
  669.          * started on the same key.
  670.          */
  671.         public void abort() throws IOException {
  672.             completeEdit(this, false);
  673.         }

  674.         private class FaultHidingOutputStream extends FilterOutputStream {
  675.             private FaultHidingOutputStream(OutputStream out) {
  676.                 super(out);
  677.             }

  678.             @Override public void write(int oneByte) {
  679.                 try {
  680.                     out.write(oneByte);
  681.                 } catch (IOException e) {
  682.                     hasErrors = true;
  683.                 }
  684.             }

  685.             @Override public void write(byte[] buffer, int offset, int length) {
  686.                 try {
  687.                     out.write(buffer, offset, length);
  688.                 } catch (IOException e) {
  689.                     hasErrors = true;
  690.                 }
  691.             }

  692.             @Override public void close() {
  693.                 try {
  694.                     out.close();
  695.                 } catch (IOException e) {
  696.                     hasErrors = true;
  697.                 }
  698.             }

  699.             @Override public void flush() {
  700.                 try {
  701.                     out.flush();
  702.                 } catch (IOException e) {
  703.                     hasErrors = true;
  704.                 }
  705.             }
  706.         }
  707.     }

  708.     private final class Entry {
  709.         private final String key;

  710.         /** Lengths of this entry's files. */
  711.         private final long[] lengths;

  712.         /** True if this entry has ever been published */
  713.         private boolean readable;

  714.         /** The ongoing edit or null if this entry is not being edited. */
  715.         private Editor currentEditor;

  716.         /** The sequence number of the most recently committed edit to this entry. */
  717.         private long sequenceNumber;

  718.         private Entry(String key) {
  719.             this.key = key;
  720.             this.lengths = new long[valueCount];
  721.         }

  722.         public String getLengths() throws IOException {
  723.             StringBuilder result = new StringBuilder();
  724.             for (long size : lengths) {
  725.                 result.append(' ').append(size);
  726.             }
  727.             return result.toString();
  728.         }

  729.         /**
  730.          * Set lengths using decimal numbers like "10123".
  731.          */
  732.         private void setLengths(String[] strings) throws IOException {
  733.             if (strings.length != valueCount) {
  734.                 throw invalidLengths(strings);
  735.             }

  736.             try {
  737.                 for (int i = 0; i < strings.length; i++) {
  738.                     lengths[i] = Long.parseLong(strings[i]);
  739.                 }
  740.             } catch (NumberFormatException e) {
  741.                 throw invalidLengths(strings);
  742.             }
  743.         }

  744.         private IOException invalidLengths(String[] strings) throws IOException {
  745.             throw new IOException("unexpected journal line: " + Arrays.toString(strings));
  746.         }

  747.         public File getCleanFile(int i) {
  748.             return new File(directory, key + "." + i);
  749.         }

  750.         public File getDirtyFile(int i) {
  751.             return new File(directory, key + "." + i + ".tmp");
  752.         }
  753.     }
  754. }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值