概述
内存缓存分析
内存缓存主要用于将可能随时需要再次使用的对象暂时添加到一个本地缓存管理中,LruCache是android系统中自带的一个缓存管理类,以下将分析具体分析LruCache的原理:
LinkedHashMap是一个数组加双向链表的结构,LruCache主要利用LinkedHashMap的有序性,将对象保存在其中,观察如下代码:
// 第三个参数用于指定accessOrder值
Map<String, String> linkedHashMap = new LinkedHashMap<>(16, 0.75f, true);
linkedHashMap.put("name1", "josan1");
linkedHashMap.put("name2", "josan2");
linkedHashMap.put("name3", "josan3");
System.out.println("开始时顺序:");
Set<Entry<String, String>> set = linkedHashMap.entrySet();
Iterator<Entry<String, String>> iterator = set.iterator();
while(iterator.hasNext()) {
Entry entry = iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println("key:" + key + ",value:" + value);
}
System.out.println("通过get方法,导致key为name1对应的Entry到表尾");
linkedHashMap.get("name1");
Set<Entry<String, String>> set2 = linkedHashMap.entrySet();
Iterator<Entry<String, String>> iterator2 = set2.iterator();
while(iterator2.hasNext()) {
Entry entry = iterator2.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println("key:" + key + ",value:" + value);
}
其输出结果为:
开始时顺序:
key:name1,value:josan1
key:name2,value:josan2
key:name3,value:josan3
通过get方法,导致key为name1对应的Entry到表尾
key:name2,value:josan2
key:name3,value:josan3
key:name1,value:josan1
LruCache正是利用了这种特性,将对象存放其中,对于访问过了对象,就将其放到最后,如果存储空间超过了限制,就从最顶部开始删除,首先被删除的,就是最近没有被get过的对象。 LruCache初始化如下:
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
从缓存中获取对象:
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
//从map中查找,找到就直接返回
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
//如果没有找到,则新建一个对象,新建方法create需要自己重写,默认返回空
V createdValue = create(key);
if (createdValue == null) {
return null;
}
//如果重写了新建对象的方法,就将其加入到map中,
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
//map的put方法如果之前没有该key,则put成功返回null,如果之前已经存在该key了,则返回之前该key下保存的对象
if (mapValue != null) {
// 如果key发生了冲突,则将原始值再次put到该key下,因为刚刚的对象是通过create创建的,所以为了安全起见,还是使用原先的对象
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
//计算对象总的大小,删除不常访问的对象
trimToSize(maxSize);
return createdValue;
}
}
将对象放入缓存中:
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
计算map大小,删除不常访问的对象,以保持map不超过设置的最大值:
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
//对象移除处理方法,重写后可做对应处理
entryRemoved(true, key, value, null);
}
}
移除对象:
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V previous;
synchronized (this) {
previous = map.remove(key);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
使用示例:
int cacheSize = 4 * 1024 * 1024; // 4MiB
LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
}
synchronized (cache) {
if (cache.get(key) == null) {
cache.put(key, value);
}
}
磁盘缓存分析
磁盘缓存类DiskLruCache实现原理与内存缓存LruCache类似,也是利用了LinkedHashMap的顺序存储特性,只是对象被当做一个文件缓存到了硬盘中,所以实现起来更加复杂。 磁盘缓存除了列表保存key数据之外,还有一个日志文件,保存缓存相关信息,内容如下:
libcore.io.DiskLruCache
1 ----->缓存版本
100 ----->app版本
2 ----->一个key保存文件数量
CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
DIRTY 335c4c6028171cfddfbaae1a9c313c52
CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
REMOVE 335c4c6028171cfddfbaae1a9c313c52
DIRTY 1ab96a171faeeee38496d8b330771a7a
CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
READ 335c4c6028171cfddfbaae1a9c313c52
READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
DIRTY:这条记录正在被创建或者更新
CLEAN:这条缓存记录已经被成功发布,后面的数据表示文件大小
READ:这条记录被读取
REMOVE:这条记录已经被移除
主要流程图如下:
初始化方法如下:
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
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
//如果日志文件已经存在了,就将日志文件中记录的内容添加到lruEntries列表中
cache.readJournal();
//将lruEntries中记录的entry正在编辑的entry删除,包括对应文件,这样的文件无法保证完整性,最好删除
cache.processJournal();
return cache;
} catch (IOException journalIsCorrupt) {
System.out
.println("DiskLruCache "
+ directory
+ " is corrupt: "
+ journalIsCorrupt.getMessage()
+ ", removing");
cache.delete();
}
}
//如果日志文件并不存在,则新建保存日志文件的目录,然后将lruEntries中的内容写入到日志文件中
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
初始化方法中主要完成如下几个操作:
1.重命名备份日志文件和日志文件
2.新建DiskLruCache
3.将现存的日志文件中的内容读取到lruEntries列表中,或者新建日志目录和日志文件,写入lruEntries中的内容
初始化过程中针对日志文件操作的三个重要方法如下:
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
//验证参数,如果不一致,则抛异常
int lineCount = 0;
while (true) {
try {
//分析读取到的一行数据,更新lruEntries中的entry或者新建entry将其加入到列表中
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
//如果最后一行不完整,则说明日志有问题,重新创建日志
if (reader.hasUnterminatedLine()) {
rebuildJournal();
} else {
//日志没什么问题,初始化journalWriter用于之后的对日志发文件的读写
journalWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(journalFile, true), Util.US_ASCII));
}
} finally {
Util.closeQuietly(reader);
}
}
//这里得到line类似于:CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
//这里主要是根据空格的index得到key
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
//如果表记录中没有该key,则新建并将其加入
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
//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)) {
//DIRTY现在正在操作,给entry设置一editor
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);
}
}
//清除currentEditor不为空的entry,该方法只在open中被调用,缓存初始化阶段不可能有正在写入的缓存,所以这样的数据是上一次缓存未完成的数据,不完成,需删除
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();
}
}
}
从缓存中获取对象
public synchronized Value get(String key) throws IOException {
checkNotClosed();
//如果该key获取到的entry为空,说明没有对应该key的缓存
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
//如果该key对应的文件不可读,也返回空
if (!entry.readable) {
return null;
}
//文件已经不存在了,返回空
for (File file : entry.cleanFiles) {
// A file must have been deleted manually!
if (!file.exists()) {
return null;
}
}
//增加冗余计数,日志中添加READ记录
redundantOpCount++;
journalWriter.append(READ);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('n');
//如果冗余计数已经大于2000并且大于表的大小,就整理一次缓存大小,删掉不经常被访问的缓存对象
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
//返回缓存对象的载体Value
return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
}
缓存对象的载体Value:
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];
}
}
整理缓存大小,清除不常用缓存,在线程池中进行
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 void trimToSize() throws IOException {
while (size > maxSize) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
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;
}
//日志中添加REMOVE记录,冗余计数加1
redundantOpCount++;
journalWriter.append(REMOVE);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('n');
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
向缓存中添加对象
添加缓存通过Editor类进行,Editor中持有Entry对象,Entry对象持有缓存文件的引用。
private final class Entry {
private final String key; //缓存key
private final long[] lengths; //缓存文件长度记录数组
File[] cleanFiles; //记录已经发布的缓存文件
File[] dirtyFiles; //记录操作中的缓存文件
private boolean readable; //是否可读
private Editor currentEditor; //当前正在编辑缓存
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 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];
}
//获得缓存文件的输入流
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;
}
}
}
//得到第index个缓存文件
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;
}
}
//将value写入到index指定的缓存文件中
public void set(int index, String value) throws IOException {
Writer writer = null;
try {
OutputStream os = new FileOutputStream(getFile(index));
writer = new OutputStreamWriter(os, Util.UTF_8);
writer.write(value);
} finally {
Util.closeQuietly(writer);
}
}
//发布这次缓存编辑
public void commit() throws IOException {
completeEdit(this, true);
committed = true;
}
......
}
像缓存中添加缓存示例如下:
DiskLruCache.Editor editor = diskCache.edit(safeKey);
if (editor == null) {
throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);
}
try {
//得到缓存文件
File file = editor.getFile(0);
//将内容写入缓存文件中
if (writer.write(file)) {
editor.commit();
}
} finally {
editor.abortUnlessCommitted();
}
清除缓存
清除缓存包括清除指定key的缓存,和清除所有缓存delete(),清除所有缓存会删除目录下的所有文件,包括日志文件
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
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;
}
最后
以上就是凶狠眼睛为你收集整理的android缓存实现分析的全部内容,希望文章能够帮你解决android缓存实现分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复