具体解说Android的图片下载框架UniversialImageLoader之磁盘缓存的扩展(二)
相对于第一篇来讲,这里讲的是磁盘缓存的延续。在这里我们主要是关注四个类。各自是DiskLruCache、LruDiskCache、StrictLineReader以及工具类Util。
接下来逐一的对它们进行剖析。
废话不多说。
首先来看一下DiskLruCache。
这个类的主要功能是什么呢?我们先来看一段类的凝视:
/** * A cache that uses a bounded amount of space on a filesystem. Each cache * entry has a string key and a fixed number of values. Each key must match * the regex <strong>[a-z0-9_-]{1,64}</strong>. Values are byte sequences, * accessible as streams or files. Each value must be between {@code 0} and * {@code Integer.MAX_VALUE} bytes in length. * <p>This class is tolerant of some I/O errors. If files are missing from the * filesystem, the corresponding entries will be dropped from the cache. If * an error occurs while writing a cache value, the edit will fail silently. * Callers should handle other problems by catching {@code IOException} and * responding appropriately. */对英文不感兴趣的童鞋别急,以下略微翻译一下:
这是基于文件系统所构建的一个基于有限空间的缓存。每个缓存入口都有一个字符串秘钥与固定的数字的序列。每个秘钥一定要与正則表達式<strong>[a-z0-9_-]{1,64}</strong>进行匹配。数值则是一些字节序列,是能够作为流或者文件被訪问的。每个数值在长度上一定是在0与最大的整数之间的。这个缓存类对部分的I/O操作室容忍的。假设有一些文件从文件系统中丢失,对应的缓存的入口ᐟ会从缓存中移除。
假设在写入一个缓存的数值的时候发生了以外的错误,
当前的编辑也会默默的撤销,回调者则会通过捕获一些I/O异常与核实的回应来处理一些其它的问题。
因为DiskLruCache的代码量较多,我们还是从一些核心的变量与方法上来讲述它。核心变量例如以下:
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; static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}"); 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 int maxFileCount; private final int valueCount; private long size = 0; private int fileCount = 0; private Writer journalWriter; private final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<String, Entry>(0, 0.75f, true); private int redundantOpCount; private long nextSequenceNumber = 0; final ThreadPoolExecutor executorService = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
上面的变量大致进行归类。包含:日志文件的命名。日志文件的開始5行内容的变量的定义,文件尺寸与数量的限制以及一个LinkedHashMap来维持一个缓存队列,最后另一个线程池。
接下来大致介绍一下里面的函数的功能:
1、readJournal 这个函数的功能是1、计算当前的日志的缓存的行数、2、计算当前的缓存的日志文件里冗余的行数 3、读取而且处理缓存的日志中的每一行的数据
2、readJournalLine 这个函数的功能是真正的运行每一行日志的操作 当在日志中遇到一个keyword的时候,从当前的缓存的度列中查看当前的keyword所映射的对象是否存在,假设不存在。以当前的keyword创建一个新的对象而且放到当前的缓存的队列中。
3、processJournal 计算初始化的尺寸,而且回收缓存中的一些垃圾。脏的入口将会被觉得是前后矛盾的,将会被回收。
4、rebuildJournal 这个函数的功能是创建一个新的删除大量冗余信息的日志文件,假设当前的日志文件存在。将会替换掉当前的日志文件。
5、
public synchronized Snapshot get(String key) throws IOException
这个函数的功能是返回一个命名为key的文件入口的快照。而且假设当前的数值是返回的,那么它将会被移动到LRU队列的头部。
6、
public synchronized boolean remove(String key) throws IOException
假设当前的文件实体是存在的而且是可以删除的。那么就删除当前的文件的实体。当前正在被编辑的文件实体是不可以被删除的。
接下来关注一下这个类中的三个比較重要的内部类,各自是Snapshot、Editor与Entry。
大致介绍一些这三个类的功能。
Snapshot是缓存的文件实体的数值的一个快照。Editor是编辑缓存的文件实体的数值。
Entry是缓存的文件实体的数据模型。
接下来我们分下一下LruDiskCache这个类的主要功能。
正如这个类的名称一样,这是一个近期最久未使用的磁盘缓存。这样这个类的大致的功能我们清楚了。
我们会在这个类中看到这样一个成员变量
protected DiskLruCache cache;
由此可见当前的类,是DiskLruCache针对磁盘缓存的接口的一个适配器。不信?我们从以下的方法中能够看出:
1、
@Override public File getDirectory() { return cache.getDirectory(); }
获取当前的缓存的文件夹。
2、
public File get(String imageUri) { DiskLruCache.Snapshot snapshot = null; try { snapshot = cache.get(getKey(imageUri)); return snapshot == null ? null : snapshot.getFile(0); } catch (IOException e) { L.e(e); return null; } finally { if (snapshot != null) { snapshot.close(); } } }
通过图片的uri的对象获取图片文件的句柄。
是通过DiskLruCache中的快照实现的。
3、
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException { DiskLruCache.Editor editor = cache.edit(getKey(imageUri)); if (editor == null) { return false; } OutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize); boolean copied = false; try { copied = IoUtils.copyStream(imageStream, os, listener, bufferSize); } finally { IoUtils.closeSilently(os); if (copied) { editor.commit(); } else { editor.abort(); } } return copied; }
利用的是DiskLruCache中的文件编辑类Editor来讲当前的文件输入流写到生成的文件里。
其余的函数也是以此类推。
接下来要讲的类StrictLineReader。我们也是可将将其理解为一个帮助类,它是专门为读取缓存日志的内容而特别设计的。
主要观察以下的一个方法:
public String readLine() throws IOException { synchronized (in) { if (buf == null) { throw new IOException("LineReader is closed"); } if (pos >= end) { fillBuf(); } for (int i = pos; i != end; ++i) { if (buf[i] == LF) { int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i; String res = new String(buf, pos, lineEnd - pos, charset.name()); pos = i + 1; return res; } } ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { @Override public String toString() { int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; try { return new String(buf, 0, length, charset.name()); } catch (UnsupportedEncodingException e) { throw new AssertionError(e); // Since we control the charset this will never happen. } } }; while (true) { out.write(buf, pos, end - pos); // Mark unterminated line in case fillBuf throws EOFException or IOException. end = -1; fillBuf(); for (int i = pos; i != end; ++i) { if (buf[i] == LF) { if (i != pos) { out.write(buf, pos, i - pos); } pos = i + 1; return out.toString(); } } } } }
在最后一个类Util中,主要是封装了三个方法。
1、readFully 从Reader中读取内容。而且拼接成为一个完整的字符串
2、deleteContents 迭代删除文件的文件夹的内容
3、closeQuietly 静默关闭文件流
Ok,关于磁盘存储的扩展就说到这里。希望对各位童鞋有所帮助。