123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927 |
- package com.yc.videosqllite.disk;
- import java.io.BufferedWriter;
- import java.io.Closeable;
- import java.io.EOFException;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.io.OutputStreamWriter;
- import java.io.Writer;
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.LinkedHashMap;
- import java.util.Map;
- import java.util.concurrent.Callable;
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.concurrent.ThreadFactory;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- /**
- * <pre>
- * @author yangchong
- * email : yangchong211@163.com
- * time : 2020/8/6
- * desc : 磁盘缓存类
- * revise:
- * </pre>
- */
- public final class DiskLruCache implements Closeable {
- /**
- * libcore.io.DiskLruCache
- * 1
- * 1
- * 1
- *
- * DIRTY 27c7e00adbacc71dc793e5e7bf02f861
- * CLEAN 27c7e00adbacc71dc793e5e7bf02f861 1208
- * READ 27c7e00adbacc71dc793e5e7bf02f861
- * DIRTY b80f9eec4b616dc6682c7fa8bas2061f
- * CLEAN b80f9eec4b616dc6682c7fa8bas2061f 1208
- * READ b80f9eec4b616dc6682c7fa8bas2061f
- * DIRTY be3fgac81c12a08e89088555d85dfd2b
- * CLEAN be3fgac81c12a08e89088555d85dfd2b 99
- * READ be3fgac81c12a08e89088555d85dfd2b
- * DIRTY 536990f4dbddfghcfbb8f350a941wsxd
- * REMOVE 536990f4dbddfghcfbb8f350a941wsxd
- *
- * 第1行:libcore.io.DiskLruCache 是固定字符串,表明使用的是 DiskLruCache 技术;
- * 第2行:DiskLruCache 的版本号,源码中为常量 1;
- * 第3行:APP 的版本号,即我们在 open() 方法里传入的版本号;
- * 第4行:valueCount,这个值也是在 open() 方法中传入的,指每个 key 对应几个文件,通常情况下都为 1;
- * 第5行:空行
- *
- *
- */
- static final String JOURNAL_FILE = "journal";
- static final String JOURNAL_FILE_TEMP = "journal.tmp";
- static final String JOURNAL_FILE_BACKUP = "journal.bkp";
- static final String MAGIC = "libcore.io.DiskLruCache";
- static final String VERSION_1 = "1";
- static final long ANY_SEQUENCE_NUMBER = -1;
- private static final String CLEAN = "CLEAN";
- private static final String DIRTY = "DIRTY";
- private static final String REMOVE = "REMOVE";
- private static final String READ = "READ";
- private final File directory;
- private final File journalFile;
- private final File journalFileTmp;
- private final File journalFileBackup;
- private final int appVersion;
- private long maxSize;
- private final int valueCount;
- private long size = 0;
- private Writer journalWriter;
- private final LinkedHashMap<String, Entry> lruEntries =
- new LinkedHashMap<String, Entry>(0, 0.75f, true);
- private int redundantOpCount;
- /**
- * To differentiate between old and current snapshots, each entry is given
- * a sequence number each time an edit is committed. A snapshot is stale if
- * its sequence number is not equal to its entry's sequence number.
- */
- private long nextSequenceNumber = 0;
- /**
- * This cache uses a single background thread to evict entries.
- */
- final ThreadPoolExecutor executorService =
- new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
- new DiskLruCacheThreadFactory());
- private final Callable<Void> cleanupCallable = new Callable<Void>() {
- public Void call() throws Exception {
- synchronized (DiskLruCache.this) {
- if (journalWriter == null) {
- return null; // Closed.
- }
- trimToSize();
- if (journalRebuildRequired()) {
- //创建一个新的日志,删除多余的信息
- rebuildJournal();
- redundantOpCount = 0;
- }
- }
- return null;
- }
- };
- private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
- this.directory = directory;
- this.appVersion = appVersion;
- this.journalFile = new File(directory, JOURNAL_FILE);
- this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
- this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
- this.valueCount = valueCount;
- this.maxSize = maxSize;
- }
- /**
- * Opens the cache in {@code directory}, creating a cache if none exists
- * there.
- * 第一个参数表示磁盘缓存在文件系统中的存储路径。
- * 第二个参数表示应用的版本号,一般设为 1 即可。当版本号发生改变时 DiskLruCache 会清空之前所有的缓存文件,
- * 而这个特性在实际开发中作用并不大,很多情况下即使应用的版本号发生了改变缓存文件却仍然是有效的,因此这个参数设为 1 比较好。
- * 第三个参数表示同一个 key 可以对应多少个缓存文件,一般设为 1 即可。
- * 第四个参数表示缓存的总大小,比如 50MB,当缓存大小超出这个设定值后,DiskLruCache 会清除一些缓存从而保证总大小不大于这个设定值。
- * @param directory a writable directory
- * @param valueCount the number of values per cache entry. Must be positive.
- * @param maxSize the maximum number of bytes this cache should use to store
- * @throws IOException if reading or writing the cache directory fails
- */
- public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
- throws IOException {
- if (maxSize <= 0) {
- throw new IllegalArgumentException("maxSize <= 0");
- }
- if (valueCount <= 0) {
- throw new IllegalArgumentException("valueCount <= 0");
- }
- // 如果存在备份日志文件,则使用它
- File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
- if (backupFile.exists()) {
- File journalFile = new File(directory, JOURNAL_FILE);
- // 如果存在正式的日志文件,则将备份日志文件删除
- if (journalFile.exists()) {
- backupFile.delete();
- } else {
- renameTo(backupFile, journalFile, false);
- }
- }
- // 首先尝试读取日志文件
- DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
- if (cache.journalFile.exists()) {
- try {
- //日志文件的读取过程
- cache.readJournal();
- //用于统计缓存文件的总体大小,并删除脏文件
- cache.processJournal();
- return cache;
- } catch (IOException journalIsCorrupt) {
- System.out
- .println("DiskLruCache "
- + directory
- + " is corrupt: "
- + journalIsCorrupt.getMessage()
- + ", removing");
- cache.delete();
- }
- }
- // 此时日志文件不存在或读取出错,新建一个DiskLruCache实例
- directory.mkdirs();
- cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
- //创建一个新的日志,删除多余的信息
- cache.rebuildJournal();
- return cache;
- }
- /**
- * 日志文件的读取过程
- * readJournal()方法其实就是通过readJournalLine(reader.readLine())方法读取日志文件中的每一行,
- * 最终会读取到lruEntries中,lruEntries是DiskLruCache在内存中的表现形式。
- * @throws IOException 异常
- */
- private void readJournal() throws IOException {
- StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), DiskUtils.US_ASCII);
- try {
- String magic = reader.readLine();
- String version = reader.readLine();
- String appVersionString = reader.readLine();
- String valueCountString = reader.readLine();
- String blank = reader.readLine();
- if (!MAGIC.equals(magic)
- || !VERSION_1.equals(version)
- || !Integer.toString(appVersion).equals(appVersionString)
- || !Integer.toString(valueCount).equals(valueCountString)
- || !"".equals(blank)) {
- throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
- + valueCountString + ", " + blank + "]");
- }
- int lineCount = 0;
- while (true) {
- try {
- //该方法用于读取每一行日志
- readJournalLine(reader.readLine());
- lineCount++;
- } catch (EOFException endOfJournal) {
- break;
- }
- }
- redundantOpCount = lineCount - lruEntries.size();
- // If we ended on a truncated line, rebuild the journal before appending to it.
- if (reader.hasUnterminatedLine()) {
- //创建一个新的日志,删除多余的信息
- rebuildJournal();
- } else {
- journalWriter = new BufferedWriter(new OutputStreamWriter(
- new FileOutputStream(journalFile, true), DiskUtils.US_ASCII));
- }
- } finally {
- DiskUtils.closeQuietly(reader);
- }
- }
- /**
- * 该方法用于读取每一行日志。日志文件的每一行都是DIRTY、CLEAN、READ或REMOVE四种行为之一,那么该方法就需要对这4中情况分别处理。
- * 大概思路:
- * 1.首先取出该行记录的key,然后根据该记录是否为REMOVE进行不同的操作,如果是REMOVE,则将该key的缓存从lruEntries中移除。
- * 2.如果不是REMOVE,说明该key存在一个对应的缓存实体Entry,则先新建一个Entry并添加到lruEntries中。
- * 3.之后再判断日志的类型,如果日志是CLEAN,代表该文件已经保存完毕了,将currentEditor设置为null;
- * 4.如果日志是DIRTY,代表文件没有保存完毕,为其currentEditor新建一个Editor。
- *
- * 为什么要这么做呢?
- * 保存一个文件时会先写入DIRTY日志,保存成功后再写入CLEAN日志,一般来说这两条日志会成对出现。
- * 这里的currentEditor相当于一个标志位,如果为空,表示文件完整,如果不为空,表示该文件是临时文件。
- * @param line 每行内容
- * @throws IOException io流异常
- */
- private void readJournalLine(String line) throws IOException {
- int firstSpace = line.indexOf(' ');
- if (firstSpace == -1) {
- throw new IOException("unexpected journal line: " + line);
- }
- int keyBegin = firstSpace + 1;
- int secondSpace = line.indexOf(' ', keyBegin);
- final String key;
- if (secondSpace == -1) {
- key = line.substring(keyBegin);
- // 如果是REMOVE,则将该key代表的缓存从lruEntries中移除
- if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
- lruEntries.remove(key);
- return;
- }
- } else {
- key = line.substring(keyBegin, secondSpace);
- }
- //取出当前key对应的缓存Entry
- Entry entry = lruEntries.get(key);
- if (entry == null) {
- entry = new Entry(key);
- lruEntries.put(key, entry);
- }
- // 如果是CLEAN、DIRTY或READ
- if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
- String[] parts = line.substring(secondSpace + 1).split(" ");
- entry.readable = true;
- entry.currentEditor = null;
- entry.setLengths(parts);
- } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
- entry.currentEditor = new Editor(entry);
- } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
- // This work was already done by calling lruEntries.get().
- } else {
- throw new IOException("unexpected journal line: " + line);
- }
- }
- /**
- * 计算初始大小并收集垃圾,作为打开缓存。假设脏条目是不一致的,将被删除。
- *
- */
- private void processJournal() throws IOException {
- deleteIfExists(journalFileTmp);
- for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
- Entry entry = i.next();
- if (entry.currentEditor == null) {
- for (int t = 0; t < valueCount; t++) {
- size += entry.lengths[t];
- }
- } else {
- entry.currentEditor = null;
- for (int t = 0; t < valueCount; t++) {
- deleteIfExists(entry.getCleanFile(t));
- deleteIfExists(entry.getDirtyFile(t));
- }
- i.remove();
- }
- }
- }
- /**
- * 创建一个新的日志,删除多余的信息。如果当前日志存在,则替换当前日志。
- */
- private synchronized void rebuildJournal() throws IOException {
- if (journalWriter != null) {
- journalWriter.close();
- }
- Writer writer = new BufferedWriter(
- new OutputStreamWriter(new FileOutputStream(journalFileTmp), DiskUtils.US_ASCII));
- try {
- writer.write(MAGIC);
- writer.write("\n");
- writer.write(VERSION_1);
- writer.write("\n");
- writer.write(Integer.toString(appVersion));
- writer.write("\n");
- writer.write(Integer.toString(valueCount));
- writer.write("\n");
- writer.write("\n");
- for (Entry entry : lruEntries.values()) {
- if (entry.currentEditor != null) {
- writer.write(DIRTY + ' ' + entry.key + '\n');
- } else {
- writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
- }
- }
- } finally {
- writer.close();
- }
- if (journalFile.exists()) {
- renameTo(journalFile, journalFileBackup, true);
- }
- renameTo(journalFileTmp, journalFile, false);
- journalFileBackup.delete();
- journalWriter = new BufferedWriter(
- new OutputStreamWriter(new FileOutputStream(journalFile, true), DiskUtils.US_ASCII));
- }
- private static void deleteIfExists(File file) throws IOException {
- if (file.exists() && !file.delete()) {
- throw new IOException();
- }
- }
- private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
- if (deleteDestination) {
- deleteIfExists(to);
- }
- if (!from.renameTo(to)) {
- throw new IOException();
- }
- }
- /**
- * 读取缓存
- * 返回名为{@code key}的条目的快照,如果不存在则返回null,这是当前不可读的。如果一个值被返回,它将被移动到LRU队列的头。
- */
- public synchronized Value get(String key) throws IOException {
- checkNotClosed();
- //取出当前key对应的缓存Entry
- Entry entry = lruEntries.get(key);
- if (entry == null) {
- return null;
- }
- if (!entry.readable) {
- return null;
- }
- for (File file : entry.cleanFiles) {
- // A file must have been deleted manually!
- if (!file.exists()) {
- // 除非用户手动删了文件, 否则不会执行到这里...
- return null;
- }
- }
- redundantOpCount++;
- journalWriter.append(READ);
- journalWriter.append(' ');
- journalWriter.append(key);
- journalWriter.append('\n');
- if (journalRebuildRequired()) {
- executorService.submit(cleanupCallable);
- }
- return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
- }
- /**
- * 返回名为{@code key}的条目的编辑器,如果正在进行另一个编辑,则返回null。
- * 这个key将会成为缓存文件的文件名
- */
- public Editor edit(String key) throws IOException {
- return edit(key, ANY_SEQUENCE_NUMBER);
- }
- /**
- * 如何写缓存?
- * 写缓存的时候需要先通过edit(String key)方法新建一个Editor,然后将数据写入Editor的输出流中,最后成功则调用
- * Editor.commit(),失败则调用Editor.abort()。
- *
- * 该方法大概思路
- * 1.取出当前key对应的缓存Entry,如果Entry不存在则新建并添加到lruEntries中,
- * 2.如果存在且entry.currentEditor不为空,表示Entry正在进行缓存编辑。
- * 3.随后新建一个Editor,并在日志文件中输出一行DIRTY日志表示开始编辑缓存文件。
- * @param key key
- * @param expectedSequenceNumber number
- * @return
- * @throws IOException 异常
- */
- private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
- checkNotClosed();
- //取出当前key对应的缓存Entry
- Entry entry = lruEntries.get(key);
- if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
- || entry.sequenceNumber != expectedSequenceNumber)) {
- return null; // Value is stale.
- }
- if (entry == null) {
- //如果Entry不存在则新建并添加到lruEntries中
- entry = new Entry(key);
- lruEntries.put(key, entry);
- } else if (entry.currentEditor != null) {
- //如果存在且entry.currentEditor不为空,表示Entry正在进行缓存编辑
- return null; // Another edit is in progress.
- }
- //随后新建一个Editor,并在日志文件中输出一行DIRTY日志表示开始编辑缓存文件。
- Editor editor = new Editor(entry);
- entry.currentEditor = editor;
- // Flush the journal before creating files to prevent file leaks.
- // 为了防止文件泄露,在创建文件前,将日志立即写入journal中
- journalWriter.append(DIRTY);
- journalWriter.append(' ');
- journalWriter.append(key);
- journalWriter.append('\n');
- journalWriter.flush();
- return editor;
- }
- /**
- * 返回该缓存存储其数据的目录。
- */
- public File getDirectory() {
- return directory;
- }
- /**
- * 返回此缓存用于存储其数据的最大字节数。
- */
- public synchronized long getMaxSize() {
- return maxSize;
- }
- /**
- * 更改缓存可以存储的最大字节数,并在必要时对作业进行排队,以精简现有的存储。
- */
- public synchronized void setMaxSize(long maxSize) {
- this.maxSize = maxSize;
- executorService.submit(cleanupCallable);
- }
- /**
- * 返回当前用于在此缓存中存储值的字节数。如果后台删除挂起,这个值可能大于最大大小。
- * 这个方法会返回当前缓存路径下所有缓存数据的总字节数,以byte为单位,
- * 如果应用程序中需要在界面上显示当前缓存数据的总大小,就可以通过调用这个方法计算出来。
- */
- public synchronized long size() {
- return size;
- }
- /**
- * 该方法首先根据文件写入是否成功来重命名或者删除tmp文件,随后向journal写入日志,最后判断是否需要清理磁盘空间。
- * @param editor editor对象
- * @param success 是否成功
- * @throws IOException
- */
- private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
- Entry entry = editor.entry;
- if (entry.currentEditor != editor) {
- throw new IllegalStateException();
- }
- // 如果当前编辑是第一次创建Entry,那么每个索引上都应该有值
- // valueCount表示一个Entry中的value数量
- if (success && !entry.readable) {
- for (int i = 0; i < valueCount; i++) {
- if (!editor.written[i]) {
- editor.abort();
- throw new IllegalStateException("Newly created entry didn't create value for index " + i);
- }
- if (!entry.getDirtyFile(i).exists()) {
- editor.abort();
- return;
- }
- }
- }
- // 遍历Entry上的每个文件
- // 如果编辑成功就将临时文件改名, 如果失败则删除临时文件
- for (int i = 0; i < valueCount; i++) {
- File dirty = entry.getDirtyFile(i);
- if (success) {
- if (dirty.exists()) {
- File clean = entry.getCleanFile(i);
- dirty.renameTo(clean);
- long oldLength = entry.lengths[i];
- long newLength = clean.length();
- entry.lengths[i] = newLength;
- size = size - oldLength + newLength;
- }
- } else {
- deleteIfExists(dirty);
- }
- }
- redundantOpCount++;
- entry.currentEditor = null;
- if (entry.readable | success) {
- entry.readable = true;
- journalWriter.append(CLEAN);
- journalWriter.append(' ');
- journalWriter.append(entry.key);
- journalWriter.append(entry.getLengths());
- journalWriter.append('\n');
- if (success) {
- // 给Entry的sequenceNumber赋值, 用于标记snapshot是否过期
- // 如果Entry和snapshot的sequenceNumber不同, 则表示数据已经过期了
- entry.sequenceNumber = nextSequenceNumber++;
- }
- } else {
- lruEntries.remove(entry.key);
- journalWriter.append(REMOVE);
- journalWriter.append(' ');
- journalWriter.append(entry.key);
- journalWriter.append('\n');
- }
- journalWriter.flush();
- // 判断是否需要清理磁盘空间
- if (size > maxSize || journalRebuildRequired()) {
- executorService.submit(cleanupCallable);
- }
- }
- /**
- * 只有当日志大小减半并消除至少2000个ops时,我们才重新生成日志。
- */
- private boolean journalRebuildRequired() {
- final int redundantOpCompactThreshold = 2000;
- return redundantOpCount >= redundantOpCompactThreshold //
- && redundantOpCount >= lruEntries.size();
- }
- /**
- * 删除{@code key}的条目,如果它存在并且可以被删除。正在编辑的条目不能删除。
- * @return true if an entry was removed.
- */
- public synchronized boolean remove(String key) throws IOException {
- checkNotClosed();
- //取出当前key对应的缓存Entry
- Entry entry = lruEntries.get(key);
- if (entry == null || entry.currentEditor != null) {
- return false;
- }
- for (int i = 0; i < valueCount; i++) {
- File file = entry.getCleanFile(i);
- if (file.exists() && !file.delete()) {
- throw new IOException("failed to delete " + file);
- }
- size -= entry.lengths[i];
- entry.lengths[i] = 0;
- }
- redundantOpCount++;
- journalWriter.append(REMOVE);
- journalWriter.append(' ');
- journalWriter.append(key);
- journalWriter.append('\n');
- lruEntries.remove(key);
- if (journalRebuildRequired()) {
- executorService.submit(cleanupCallable);
- }
- return true;
- }
- /**
- * Returns true if this cache has been closed.
- */
- public synchronized boolean isClosed() {
- return journalWriter == null;
- }
- private void checkNotClosed() {
- if (journalWriter == null) {
- throw new IllegalStateException("cache is closed");
- }
- }
- /**
- * 强制对文件系统进行缓冲操作。
- * 这个方法用于将内存中的操作记录同步到日志文件(也就是journal文件)当中。
- * 这个方法非常重要,因为DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容。
- * 并不是每次写读缓存都要调用一次flush()方法,频繁地调用并不会带来任何好处,只会额外增加同步journal文件的时间。
- * 比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了。
- */
- public synchronized void flush() throws IOException {
- checkNotClosed();
- trimToSize();
- journalWriter.flush();
- }
- /**
- * 这个方法用于将DiskLruCache关闭掉,是和open()方法对应的一个方法。
- * 关闭掉了之后就不能再调用DiskLruCache中任何操作缓存数据的方法
- * 通常只应该在Activity的onDestroy()方法中去调用close()方法。
- */
- public synchronized void close() throws IOException {
- if (journalWriter == null) {
- return; // Already closed.
- }
- for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
- if (entry.currentEditor != null) {
- entry.currentEditor.abort();
- }
- }
- trimToSize();
- journalWriter.close();
- journalWriter = null;
- }
- private void trimToSize() throws IOException {
- while (size > maxSize) {
- Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
- remove(toEvict.getKey());
- }
- }
- /**
- * 关闭缓存并删除其存储的所有值。这将删除缓存目录中的所有文件,包括不是由缓存创建的文件。
- */
- public void delete() throws IOException {
- close();
- DiskUtils.deleteContents(directory);
- }
- private static String inputStreamToString(InputStream in) throws IOException {
- //写数据,一次读取一个字节
- return DiskUtils.readFully(new InputStreamReader(in, DiskUtils.UTF_8));
- }
- /**
- * A snapshot of the values for an entry.
- */
- public final class Value {
- private final String key;
- private final long sequenceNumber;
- private final long[] lengths;
- private final File[] files;
- private Value(String key, long sequenceNumber, File[] files, long[] lengths) {
- this.key = key;
- this.sequenceNumber = sequenceNumber;
- this.files = files;
- this.lengths = lengths;
- }
- /**
- * Returns an editor for this snapshot's entry, or null if either the
- * entry has changed since this snapshot was created or if another edit
- * is in progress.
- */
- public Editor edit() throws IOException {
- return DiskLruCache.this.edit(key, sequenceNumber);
- }
- public File getFile(int index) {
- return files[index];
- }
- /**
- * Returns the string value for {@code index}.
- */
- public String getString(int index) throws IOException {
- InputStream is = new FileInputStream(files[index]);
- return inputStreamToString(is);
- }
- /**
- * Returns the byte length of the value for {@code index}.
- */
- public long getLength(int index) {
- return lengths[index];
- }
- }
- /**
- * 编辑条目的值。
- */
- public final class Editor {
- private final Entry entry;
- private final boolean[] written;
- private boolean committed;
- private Editor(Entry entry) {
- this.entry = entry;
- this.written = (entry.readable) ? null : new boolean[valueCount];
- }
- /**
- * 返回一个未缓冲的输入流来读取最后提交的值,如果没有提交值,则返回null。
- */
- private InputStream newInputStream(int index) throws IOException {
- synchronized (DiskLruCache.this) {
- if (entry.currentEditor != this) {
- throw new IllegalStateException();
- }
- if (!entry.readable) {
- return null;
- }
- try {
- return new FileInputStream(entry.getCleanFile(index));
- } catch (FileNotFoundException e) {
- return null;
- }
- }
- }
- /**
- * 以字符串的形式返回最后一个提交的值,如果没有提交值,则返回null。
- */
- public String getString(int index) throws IOException {
- //创建io流对象
- InputStream in = newInputStream(index);
- //如果流对象不为空,则写入数据
- return in != null ? inputStreamToString(in) : null;
- }
- public File getFile(int index) throws IOException {
- synchronized (DiskLruCache.this) {
- if (entry.currentEditor != this) {
- throw new IllegalStateException();
- }
- if (!entry.readable) {
- written[index] = true;
- }
- File dirtyFile = entry.getDirtyFile(index);
- if (!directory.exists()) {
- directory.mkdirs();
- }
- return dirtyFile;
- }
- }
- /**
- * Sets the value at {@code index} to {@code value}.
- */
- public void set(int index, String value) throws IOException {
- Writer writer = null;
- try {
- File file = getFile(index);
- //创建输出流对象
- OutputStream os = new FileOutputStream(file);
- writer = new OutputStreamWriter(os, DiskUtils.UTF_8);
- //写数据
- writer.write(value);
- } finally {
- //关闭流对象
- DiskUtils.closeQuietly(writer);
- }
- }
- /**
- * 成功后调用Editor.commit()
- * 提交此编辑,使其对读者可见。这释放了编辑锁,因此可以在同一键上启动另一个编辑。
- */
- public void commit() throws IOException {
- // The object using this Editor must catch and handle any errors
- // during the write. If there is an error and they call commit
- // anyway, we will assume whatever they managed to write was valid.
- // Normally they should call abort.
- completeEdit(this, true);
- committed = true;
- }
- /**
- * 失败后调用Editor.abort()方法
- * 中止这个编辑。这释放了编辑锁,因此可以在同一键上启动另一个编辑。
- */
- public void abort() throws IOException {
- completeEdit(this, false);
- }
- public void abortUnlessCommitted() {
- if (!committed) {
- try {
- abort();
- } catch (IOException ignored) {
- }
- }
- }
- }
- private final class Entry {
- private final String key;
- /**
- * Lengths of this entry's files.
- */
- private final long[] lengths;
- /**
- * Memoized File objects for this entry to avoid char[] allocations.
- */
- File[] cleanFiles;
- File[] dirtyFiles;
- /**
- * True if this entry has ever been published.
- */
- private boolean readable;
- /**
- * The ongoing edit or null if this entry is not being edited.
- */
- private Editor currentEditor;
- /**
- * The sequence number of the most recently committed edit to this entry.
- */
- private long sequenceNumber;
- private Entry(String key) {
- this.key = key;
- this.lengths = new long[valueCount];
- cleanFiles = new File[valueCount];
- dirtyFiles = new File[valueCount];
- // The names are repetitive so re-use the same builder to avoid allocations.
- StringBuilder fileBuilder = new StringBuilder(key).append('.');
- int truncateTo = fileBuilder.length();
- for (int i = 0; i < valueCount; i++) {
- fileBuilder.append(i);
- cleanFiles[i] = new File(directory, fileBuilder.toString());
- fileBuilder.append(".tmp");
- dirtyFiles[i] = new File(directory, fileBuilder.toString());
- fileBuilder.setLength(truncateTo);
- }
- }
- public String getLengths() throws IOException {
- StringBuilder result = new StringBuilder();
- for (long size : lengths) {
- result.append(' ').append(size);
- }
- return result.toString();
- }
- /**
- * Set lengths using decimal numbers like "10123".
- */
- private void setLengths(String[] strings) throws IOException {
- if (strings.length != valueCount) {
- throw invalidLengths(strings);
- }
- try {
- for (int i = 0; i < strings.length; i++) {
- lengths[i] = Long.parseLong(strings[i]);
- }
- } catch (NumberFormatException e) {
- throw invalidLengths(strings);
- }
- }
- private IOException invalidLengths(String[] strings) throws IOException {
- throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
- }
- public File getCleanFile(int i) {
- return cleanFiles[i];
- }
- public File getDirtyFile(int i) {
- return dirtyFiles[i];
- }
- }
- /**
- * A {@link ThreadFactory} that builds a thread with a specific thread name
- * and with minimum priority.
- */
- private static final class DiskLruCacheThreadFactory implements ThreadFactory {
- @Override
- public synchronized Thread newThread(Runnable runnable) {
- //设置线程优先级
- Thread result = new Thread(runnable, "video-disk-lru-cache-thread");
- result.setPriority(Thread.MIN_PRIORITY);
- return result;
- }
- }
- }
|