LruDiskCache要点--不可不用的磁盘缓存工具类
来源:互联网 发布:淘宝批量上架软件 编辑:程序博客网 时间:2024/06/03 20:46
LruDiskCache是使用Lru算法的磁盘缓存类,它的功能是将LruCache中缓存位置由内存改为磁盘,一般两者结合使用,用于对处理小文件,图片的缓存。
下面记录下阅读过程中几个比较重要的点:
Get
获取缓存数据时,LruDiskCache会使用LinkedHashmap的算法,也就是最常使用的放在尾部,最少使用的首先被遍历到.
当你需要获取缓存数据时,首先会得到是一个Snapshot对象(如果数据正常的话:写入成功、在有效内等等),Snapshot其实就是持有缓存文件的输入流,无其它逻辑操作。
private synchronized Snapshot getByDiskKey(String diskKey) throws IOException { checkNotClosed(); Entry entry = lruEntries.get(diskKey); if (entry == null) { return null; } if (!entry.readable) {//数据是否被写入成功 return null; } // 判断时间有效性 if (entry.expiryTimestamp < System.currentTimeMillis()) { 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(DELETE + " " + diskKey + '\n'); lruEntries.remove(diskKey); if (journalRebuildRequired()) { executorService.submit(cleanupCallable); } return null; } //同一个key可能对应多个缓存 FileInputStream[] ins = new FileInputStream[valueCount]; try { for (int i = 0; i < valueCount; i++) { ins[i] = new FileInputStream(entry.getCleanFile(i)); } } catch (FileNotFoundException e) { // A file must have been deleted manually! for (int i = 0; i < valueCount; i++) { if (ins[i] != null) { IOUtils.closeQuietly(ins[i]); } else { break; } } return null; } redundantOpCount++; journalWriter.append(READ + " " + diskKey + '\n'); if (journalRebuildRequired()) { executorService.submit(cleanupCallable); } return new Snapshot(diskKey, entry.sequenceNumber, ins, entry.lengths); }
Set
增加缓存数据时,需先调用edit方法,获得Editor对象,或者null(已经是edit状态时),并写入一条update日志,该条日志并不是写入缓存成功的标识。
注:diskKey为原key经过md5加密后的值。
private synchronized Editor editByDiskKey(String diskKey, long expectedSequenceNumber) throws IOException { checkNotClosed(); Entry entry = lruEntries.get(diskKey); if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) { return null; // Snapshot is stale. } if (entry == null) { entry = new Entry(diskKey); lruEntries.put(diskKey, entry); } else if (entry.currentEditor != null) { return null; // Another edit is in progress. } Editor editor = new Editor(entry); entry.currentEditor = editor; // Flush the journal before creating files to prevent file leaks. journalWriter.write(UPDATE + " " + diskKey + '\n'); journalWriter.flush(); return editor; }
然后通过Editor对象获得文件的操作流FaultHidingOutputStream对象,该对象在操作文件出错的时候会将hasErrors变量赋值为false,该变量对最终插入数据成功与否起关键性的作用。
代码中entry.getDirtyFile(index),可能有同学有疑问,为什么是Dirty。其实这里只是作为一个临时文件,在数据写入成功后会将改文件重命名后做为正式文件。
readable表示当前Entry的数据是否被写入过了,如果是,则不能再重复写入。
public OutputStream newOutputStream(int index) throws IOException { synchronized (LruDiskCache.this) { if (entry.currentEditor != this) { throw new IllegalStateException(); } if (!entry.readable) { written[index] = true; } File dirtyFile = entry.getDirtyFile(index); FileOutputStream outputStream; try { outputStream = new FileOutputStream(dirtyFile); } catch (FileNotFoundException e) { // Attempt to recreate the cache directory. directory.mkdirs(); try { outputStream = new FileOutputStream(dirtyFile); } catch (FileNotFoundException e2) { // We are unable to recover. Silently eat the writes. return NULL_OUTPUT_STREAM; } } return new FaultHidingOutputStream(outputStream); } }
最终新增加缓存,需调用Editor对象的commit方法;
在commit时,会进行判断,如果写入成功,刚加一条clean日志(clean才表示数据插入成功了)
entry.readable =
true
;
journalWriter.write(CLEAN +
" "
+ entry.diskKey +
" "
+ EXPIRY_PREFIX + entry.expiryTimestamp + entry.getLengths() +
'\n'
);
否则做为脏数据处理,删除文件并写入删除日志。
journalWriter.write(DELETE +
" "
+ entry.diskKey +
'\n'
);
Delete
删除缓存时,先判断是不是edit状态,不是才能执行删除操作,并将删除记录写入日志文件。
如果同一key对应多个缓存文件,则全删。
private synchronized boolean removeByDiskKey(String diskKey) throws IOException { checkNotClosed(); Entry entry = lruEntries.get(diskKey); 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(DELETE + " " + diskKey + '\n'); lruEntries.remove(diskKey); if (journalRebuildRequired()) { executorService.submit(cleanupCallable); } return true; }
其它分析:
许多操作的结尾处都会添加一个整理数据的任务,保证数据在可控范围:
private final Callable<Void> cleanupCallable = new Callable<Void>() { public Void call() throws Exception { synchronized (LruDiskCache.this) { if (journalWriter == null) { return null; // Closed. } trimToSize();//超过大小时删除不常用数据 if (journalRebuildRequired()) {//超过2000条日志后需要重建日志 rebuildJournal();//根据现在的数据生成新的日志文件,重命名原有的日志文件 redundantOpCount = 0;//初始化 } } return null; } };
日志文件是有限制长度的,不能随意增长:
private boolean journalRebuildRequired() { final int redundantOpCompactThreshold = 2000; return redundantOpCount >= redundantOpCompactThreshold // && redundantOpCount >= lruEntries.size(); }
有些操作会遍历valueCount,这个值表示同一个key下的多个缓存,比如一张图片可分为大中小,这时候就以valueCount来做区分.
(上述代码来源于xutils中的LruDiskCache,相对于Android官网内的DiskLruCache,它显然更加完善。)
- LruDiskCache要点--不可不用的磁盘缓存工具类
- LruDiskCache
- Fresco磁盘内存缓存 工具类
- 不可变类的缓存
- 缓存实例的不可变类
- 缓存实例的不可变类
- 缓存实例的不可变类
- 缓存实例的不可变类
- 缓存不可变类的实例
- 缓存实例的不可变类:
- web开发设计人员不可不用的在线web工具和应用
- 10款“程序猿”和“射击湿”不可不用的在线资源和工具网站
- 10款“程序猿”和“射击湿”不可不用的在线资源和工具网站 #故事
- 10款“程序猿”和“射击湿”不可不用的在线资源和工具网站
- 磁盘缓存的使用DiskLruCache类
- .NET开发不可不知、不可不用的辅助类(一)
- .NET开发不可不知、不可不用的辅助类(二)
- .NET开发不可不知、不可不用的辅助类(摘录收藏)
- 从一行代码里面学点JavaScript
- Android平台
- 初次认识Ngnix
- vc内存泄漏问题(转)
- TOMCAT多域名绑定实现
- LruDiskCache要点--不可不用的磁盘缓存工具类
- CSpreadSheet类
- CV牛人、研究小组、paper、代码、最新动态链接
- Nginx编译与安装
- 为什么很多创业项目还没开始就注定失败
- 关于hierachyViewer源代码导入运行的问题
- 具有动态效果的响应式设计
- 技术规划
- bzoj 2002