最近最少使用缓存官员最近(LruDiscCache)之前,引入一个概念,一个重要的三个班:
key:这是DiscCacheAware接口save里面的方法imageUri通过调用参数FileNameGenerator的generate(imageUri)所生成的字符串,key必须满足[a-z0-9_-]{1,64}。相应着Entry,Snapshot以及Editor的key字段。
通过以例如以下方法来检測key的合法性
privatevoid validateKey(String key) { Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); if (!matcher.matches()) { thrownew IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: "" + key + """); }
Entry:是DiskLruCache里面的内部类,每个key相应多个缓存图片文件,保存文件的个数是由Entry中lengths数组的长度来决定的,假设追根就地的话是valueCount来决定Entry中封装的文件的个数。
(只是在实际初始化缓存的过程中,该每个Entry中仅仅有一个图片缓存文件与之相应。也就是说一个key相应一个file)
该类仅仅有一个构造函数用来初始化key和lengths数组 |
||||
属性名称 |
说明 |
类型 |
||
key |
每个key相应多个file,该能够是图片文件 |
String |
||
readable |
当当前Entry对象被公布的时候设置为true |
boolean |
||
currentEditor |
假设当前entry没有被编辑那么该属性就位null |
Editor |
||
sequenceNumber |
对当前entry近期提交的编辑做的标志序列号,每成功提交一次,当然entry的该字段就++ |
long |
||
lengths |
当前entry中的每个file的长度。在构造器中初始化。初始化长度为valueCount,实际上该lengths固定长度为1,也就是一个Entry代表了一个文件的缓存实体 |
long[] |
||
方法名 |
方法说明 |
返回值 |
||
getLengths() |
返回当前entry对象中全部文件的总长度 |
String |
||
setLengths(String[] strs) |
採用十进制的数字来设置每个entry所封装的file的长度。来这是lengths数组每个元素的值,通过读取日志的CLEAN行数据时调用此方法 |
void |
||
getCleanFile(int i) |
获取当前entry中某一个干净(有效)的缓存文件(图片),文件名的格式为key+”.”+i |
File |
||
getDirtyFile(int i) |
获取当前entry中每个脏的(无效)的缓存文件(图片)。名称的格式为key+”.”+i+”.tmp” |
File |
||
Editor(编辑器):每个entry对象又都包括一个Editor,Editor类也是DiskLruCache的一个final类型的内部类;用来负责保存Entry中每个图片File的文件输出流和输入流,以便于图片缓存文件的输出和读取,在调用缓存的save方法的时候就是获取方法參数imageUri所生成的key相应Entry中的一个Editor对象,从而获取imageUri图片的输出流来把图片写入到缓存中(directory文件夹中)。
该类提供一个构造器用来初始化,用来所要编辑的entry和written数组 |
|||
属性 |
说明 |
类型 |
|
entry |
final,代表当前Editor对象的所要编辑的entry对象 |
Entry |
|
written |
final,在构造器中初始化,假设entry已经公布的话就设置为null,否则就初始化长度为valueCount的数组,它的每个元素用来标志entry中相应索引文件是否可写。 |
boolean[] |
|
hasErrors |
编辑是否出错,当把entry中的一个File输出时发生IO异常时设置为true,详细在Editor的内部类FaultHidingOutputStream中设置 |
boolean |
|
committed |
编辑是否已经提交 |
boolean |
|
方法名 |
方法说明 |
返回类型 |
|
newInputStream(int index) |
该方法返回一个无缓冲的输入流用来读取entry中第index条数据(也就是第index个图片文件,实际应用因为Entry中值相应一个文件所以index固定位0)上次提交的值,假设该条数据没有被提交的值就返回null。 1) |
InputStream |
|
getString(index) |
获取entry中第index文件上次提交的值 |
String |
|
newOutputStream(int index) |
Entry中相应文件的输出流,向缓存写入数据时调用。目的是把图片文件保存到缓存。 调用save方法时调用。获取entry中第index文件(也就是第index个图片文件。实际应用因为Entry中值相应一个文件所以index固定位0)上的无缓冲的输出流用来把文件输出到缓存, 假设输出的过程中发成异常就设置Editor的hasErrors为true,即为编辑失败 |
OutputStream |
|
set(index,String value) |
向当然Editor中entry的第index个文件写入数据value |
value |
|
commit() |
当编辑完毕后调用这种方法使得该File对reader可见。同一时候释放线程锁以便于让其它editor对象对同一个key上的entry进行编辑操作。运行过程例如以下: 1) 推断hasError是否为true,假设为true,则撤销此次提交 2)设置commited为true |
void |
|
abort() |
终止对当前entry的第index文件的编辑操作。实际上是调用completeEdit(this,false)来撤销此次编辑,并释放锁 |
void |
|
abortUnlessCommitted() |
在没有提交的情况下,也就是commited=false的情况下终止本次编辑 |
||
Snapshot: 每个Entry又有一个Snapshot(快照),当从缓存中调用DisCacheAware方法中的get(String iamgeUri)获取缓存图片时实际上获取的不是Entry。而是imageUri生成key相应Entry的一个快照Snapshot对象所封装的File对象
该类实现了Closeable,能够使用Java7的新特性 用try-with-resource来自己主动关闭流,该类包括的字段都在构造函数中进行初始化 |
||
属性名 |
说明 |
类型 |
key |
entry的key |
string |
sequenceNumber |
entry的sequenceNumber |
long |
files[] |
entry中全部的file(实际上该files的长度仅仅有一) |
File[] |
ins |
entry中全部file的输入流 |
InputStream[] |
lengths[] |
entry全部file的总大小 |
long[] |
edit() |
返回该快照所相应的entry的Editor对象 |
|
getFile(int index) |
获取快照中的第index个文件。从缓存中取出数据时调用 |
File |
getInputStream(int index) |
获取ins数组中第index个文件的输入流 |
InputStream |
getString(int index) |
把第index文件里的内容作为字符串返回 |
String |
close() |
循环遍历ins。关闭每个输入流 |
void |
所以Entry,Editor。Snapshot之间的关系通过key串联了起来:
日志文件:该缓存提供了一个名叫journal的日志文件,典型的日志文件看清来例如以下格式
每一个日志文件的开头前面五行数据分别为
行号 |
该行的数据 |
1 |
libcore.io.DiskLruCache |
2 |
该缓存的版本 比如1 |
3 |
app的版本 比如100 |
4 |
每个Entry中所相应的File的数目比如2 |
5 |
空白行 |
第五行过后就是日志的正文,日志正文的格式例如以下
CLEAN行所代表的数据格式例如以下
CLEAN |
entry所代表的key |
f1.length |
f2.length |
……………. |
|
特别说明:f1.length 是key所相应的entry中第一个文件的大小,和f2.length之间用一个空格隔开,详细fn是多少由日志中第四行的数据所决定。假设追根究底的话,是由Entry对象中的lengths数组的长度来决定或者是DiskLruCache的valueCount字段来决定(由于lengths数组的长度初始化的时候就是valueCount)。
REMOVE行READ行以及DIRTY行显示的数据格式较为简单:
REMOVE |
entry对象的key |
|
|
READ |
同上 |
|
|
DIRTY |
同上 |
|
以下是源码中给出的日志样本文件。如图
每行字段的被写入日志文件的时机例如以下表:
DIRTY |
写入该行数据的时机有两处: 1) 调用rebuildJournal又一次新的日志文件时会把lruEntries中处于编辑状态的entry(entry.currentEditor!=null的状态)写入日志文件里去(日志格式件见上文) 2) 调动edit方法对entry进行编辑或者说调用save方法时会把当前的entry写入到日志文件 |
CLEAN |
写入该行数据的时机有两处: 1) 调用rebuildJournal又一次新的日志文件时会把lruEntries中处于非编辑状态的entry(entry.currentEditor==null的状态)写入日志文件里去(日志格式件见上文) 2) completeEdit方法中当前entry处于公布状态(readable=true)或者编辑成功的时候(success=true的时候)写入 |
READ |
当调用get(key)方法获取entry的快照时会写入 |
REMOVE |
写入该条数据的世纪有两处 1) 在completeEdit方法中假设当然Entry既不处于公布状态并且方法參数success为false的时候写入,并且把相应的缓存文件也从缓存中删除。 2) 调用remove(String key)方法删除key相应的缓存文件时写入,而且把相应的缓存文件也从缓存中删除。 |
既然有写入日志文件的时机,那么肯定也会提供一个读取日志文件的时机,详细的时机以下解说open方法初始化缓存的时候会讲到。
当缓存被操作的时候日志文件就会被追加进来。
日志文件偶尔会通过丢掉多余行的数据来实现对日志的简化;在对日志进行简化操作的过程中会用到一个名为journal.tmp的暂时文件,当缓存被打开的情况下假设journal.tmp文件存在的话就会被删除。
在DiskLruCache类中与日志相关的字段例如以下所看到的:
staticfinal String JOURNAL_FILE = "journal"; staticfinal String JOURNAL_FILE_TEMP = "journal.tmp"; staticfinal String JOURNAL_FILE_BACKUP = "journal.bkp"; staticfinal String MAGIC = "libcore.io.DiskLruCache"; staticfinal String VERSION_1 = "1"; staticfinallongANY_SEQUENCE_NUMBER = -1; staticfinal Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}"); privatestaticfinal String CLEAN = "CLEAN"; privatestaticfinal String DIRTY = "DIRTY"; privatestaticfinal String REMOVE = "REMOVE"; privatestaticfinal String READ = "READ"; privatefinal File journalFile; //调用rebuildJorunal又一次创建日志文件的时候会把日志信息写入到该文件里去 privatefinal File journalFileTmp; privatefinal File journalFileBackup; privatefinalintappVersion; private Writer journalWriter; private int reduantOpCount;//用来推断是否重建日志的字段 private int redundantOpCount;
注意:当中的journalWriter,该对象用来向日志文件里写入数据,同一时候该对象是否为null是作为缓存是否关闭的决定条件:以下三个方法能够说明这个结论
//检測缓存是否关闭 publicsynchronizedboolean isClosed() { returnjournalWriter ==null; } //检測缓存是否未关闭 privatevoid checkNotClosed() { if (journalWriter ==null) { thrownew IllegalStateException("cache is closed"); } } /** * 关闭缓存。journalWirter为空的话,说明缓存已经关闭:方法调用结束 * 否则的话循环遍历lruEntries中的每个Entry,撤掉正在编辑的Entry。 * Closes this cache. Stored values will remain on thefilesystem. */ publicsynchronizedvoid close()throws IOException { if (journalWriter ==null) { return;//缓存已经关闭,直接退出方法调用 } //对处于编辑中的entry进行撤销编辑操作 for (Entry entry :new ArrayList<Entry>(lruEntries.values())) { if (entry.currentEditor !=null) { entry.currentEditor.abort(); } } trimToSize(); trimToFileCount(); //关闭日志输出流 journalWriter.close(); journalWriter =null; }
---------------------------------------------------------------------------------------------------
事实上。这个类中大部分的方法都是再操作这些日志文件,当然日志文件的大小也有限制,而这个限制就是有redundantOpCount字段决定的,假设例如以下方法返回true的话就又一次建立一个新的日志文件,并把原来的日志文件删除掉
//推断是否须要重建日志文件当 privatebooleanjournalRebuildRequired() { finalint redundantOpCompactThreshold = 2000; returnredundantOpCount >= redundantOpCompactThreshold && redundantOpCount >= lruEntries.size(); }
redundantOpCount的值有在四处进行了设定:
get(String key),remove(Stringkey) completeEdit没调用一次这种方法就会对redundantOpCount进行++操作,而读取日志的方法readJournal则对该字段赋值为redundantOpCount = lineCount - lruEntries.size();其实这个readJournal是在调用open方法初始化缓存的时候调用的,也就相当于对redundantOpCount进行了初始化操作。
同一时候当journalRebuildRequired的时候redundantOpCount进行清零操作
-------------------------------------------------------------------------------
介绍了上面的一些基本概念以下说说详细怎么使用这个缓存(介绍流程为:初始化缓存,向缓存中存取数据。从缓存中删除数据以及关闭缓存来进行说明)以及LRU算法实现是怎么体现的。
初始化缓存
因为DiskLruCache的构造函数是私有的,所以不能在外部进行该对象的初始化;DiskLruCache提供了一个静态的open()方法来进行缓存的初始化:
该方法进行例如以下操作
1) 创建日志文件:主要是对日志备份文件journal.bkp进行处理,假设journal.bkp文件和journal都存在的话就删除journal.bkp文件,假设journal.bkp文件存在而journal文件不存在就把journal.bkp重命名为journal文件。
2) 调用构造器进行缓存对象cache的初始化,初始化的的数据包含日志的三个文件:journal, journal.tmp,journal.bkp;同一时候还初始化了每个Entry锁能存储的文件的个数valueCount,缓存的最大内存maxSize和最多缓存多少个文件的maxFileCount;
3) 假设cache.journalFile.exists()==true而且读取日记操作没有IO错误的话,就直接返回上面的cache,否者就又一次初始化缓存对象。
也即是说打开缓存的时候有可能初始化两次DiskLruCache的对象,第一次初始化cache1的时候会推断日志文件journalFile是否存在,不存在的话就进行第二次初始化cache2;假设存在的话就进行对journalFile进行IO操作,假设没有出现异常的情况下直接返回cache1,否则返回cache2.逻辑代码的处理例如以下:
DiskLruCache cache = new DiskLruCache();//第一次初始化 if(cache.journalFile.exists()){ try{ 对日志文件进行IO操作。详细操作的逻辑下文描写叙述 return cache; }catch(IOException journalFileException){ 操作日志出现IO错误 cache.delete();删除缓存 } } cache.makDir(); cache = new DiskLruCache();//第二次初始化 cache.rebuildJournalFile(); return cache;
上文刚说过初始化的时候须要读日志进行读取操作,以下重点说说初始化缓存的时候对日志文件进行了哪些操作。
1) 读取日志的方法是由readJournal()来对日式文件journal一行一行的读取,对每一行日志文件的处理是由readJournalLine(String line)方法来决定的,对每一行日式数据的处理实际上式对每行日志的key在lruEnries中相应Entry对象的处理。对每一行文件的处理例如以下表:
DIRTY |
当读取该条数据的时候,就实例化该key相应entry对象的currentEditor使之处于编辑状态 |
CLEAN |
设置该条数据key相应的Entry的为公布状态,而且设置currentEditro=null |
READ |
对该条数据不作处理 |
REMOVE |
当调用open方法读取日志文件的时候,改行数据中key锁相应的那个实体会从lruEntries中删除。 注意:该key相应的Entry所代表的那个file文件在缓存已经删除 |
注意:除了读取到REMOVE行直接在lruEntries中删除相应的Entry之外,其余的每一行数据的须要进行例如以下推断然后在进行处理,主要是向lruEntries中加入Entry对象,(这是第一次加入):
附带详细方法实现:
//读入日志的一行数据 privatevoid readJournalLine(String line)throws IOException { //获取第一个空格的位置 int firstSpace = line.indexOf(' '); int keyBegin = firstSpace + 1; //获取第二个空格的位置 int secondSpace = line.indexOf(' ', keyBegin); //获取该行数据代表的所代表的key final String key; if (secondSpace == -1) {//假设第二个空格不存在 key = line.substring(keyBegin); //假设改行数据是以REMOVE开头的情况下。就从lruEntries中删除该key锁代表的entry 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); //假设没有就创建一个Entry。并放入lruEntries中 lruEntries.put(key, entry); } //假设改行数据已ClEAN开头 if (secondSpace != -1 && firstSpace ==CLEAN.length() && line.startsWith(CLEAN)) { String[] parts = line.substring(secondSpace + 1).split(" "); entry.readable =true;//设置给Entry为公布状态 entry.currentEditor =null;//编辑对象设置为空 entry.setLengths(parts);//设置该entry中每个file的大小 } //假设该行数据已DIRTY开头,那么设置key所相应的entry为编辑状态 elseif (secondSpace == -1 && firstSpace ==DIRTY.length() && line.startsWith(DIRTY)) {//假设该条数据是以DIRTY开头,就将该entry设置为编辑状态 entry.currentEditor =new Editor(entry); } elseif (secondSpace == -1 && firstSpace ==READ.length() && line.startsWith(READ)) { // This work was already done by calling lruEntries.get(). } else { thrownew IOException("unexpected journal line: " + line); } }
2) 调用processJournal()对日志文件进一步处理:遍历lruEntries(lruEntires中的数据在步骤1中的readJournalLine方法中加入的)中的每个Entry对象,对同编辑状态的Entry进行不同的处理;对于处于非编辑状态的entry。
也就是entry.currentEditor==null的entry,计算他们的总的文件数目fileCount以及总的文件的大小size;而entry.currentEditor!=null的Entry(注意是由日志文件里的DIRTY相应的Entry),这些,删除这些entry相应的每个file,也即是说直接从缓存中删除了这些缓存文件。
privatevoidprocessJournal()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]; fileCount++; } } else { entry.currentEditor =null; for (int t = 0; t <valueCount; t++) { deleteIfExists(entry.getCleanFile(t)); deleteIfExists(entry.getDirtyFile(t)); } i.remove(); } } }
3) 初始化journalWriter
ache.journalWriter =new BufferedWriter( new OutputStreamWriter(new FileOutputStream(cache.journalFile,true), Util.US_ASCII))
到此完毕了对缓存的初始化操作。注意这是在读取日志文件时没有抛出异常的时候完毕的初始化,假设抛出异常的话,就再次进行例如以下初始化
directory.mkdirs(); cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount); cache.rebuildJournal();//把原来的日志文件删除,并又一次创建一个写入前五行数据的日志文件。以及又一次创建journalWirt到如今终于的缓存初始化操作才算真正的完毕!
数据写入缓存
向缓存写入数据时通过DiscCacheWare的两个save方法来实现的:核心思想是从lruEntries中获取指定key(如果该key为123456)相应的Entry对象(如果没有就往lruEntries中加入)。然后获取该Entry对象的Editor对象editor,并返回editor。由于这个editor包括了文件的输出流,用该输出流来想缓存中写入数据,从而达到缓存图片的目的。(注意此时向journal文件里写入了DIRTY记录)>
此时lruEntries中和journal文件里的包括的数据例如以下:
DiskLruCache.Editor editor = cache.edit(getKey(imageUri));//这个editor是一个新的编辑器对象 privatesynchronized Editor edit(String key,long expectedSequenceNumber)throws IOException { //检測缓存是否关闭 checkNotClosed(); //检測key是否符合规则 validateKey(key); //从lruEntries中获取指定对象的key Entry entry = lruEntries.get(key); if (expectedSequenceNumber !=ANY_SEQUENCE_NUMBER && (entry ==null || entry.sequenceNumber != expectedSequenceNumber)) { returnnull;//快照已经陈旧 } //假设在lruEntries中没有相应的entry对象,则创建并加入 if (entry ==null) { entry = new Entry(key); lruEntries.put(key, entry); }//假设Entry存在而且还处于编辑状态的话就返回一个null elseif (entry.currentEditor != null) { returnnull;// Another edit is in progress. } //创建一个新的编辑器对象 Editor editor = new Editor(entry); entry.currentEditor = editor; // Flush the journal before creating files to prevent file leaks. //创建新的文件之前刷新Writer以阻止文件泄露 journalWriter.write(DIRTY +' ' + key + ' '); journalWriter.flush(); return editor; }写入缓存成功后调用editor.commit()来完毕保存数据的操作。(注意此时向journal文件里写入了CLEAN记录)
假设失败的话调用editor.abort()来撤掉此次的编辑,同一时候从lruEntries中删除此entry.(注意此时向journal文件里写入了REMOVE记录)
从缓存中取数据:
从缓存中取数据是通过调用DiscCacheWare的get(String imageUri)方法来实现的。前面说过。从缓存中取数据的时候获取的实际上是一个Entry的Snapshot。详细的方法例如以下:
publicsynchronized Snapshot get(String key)throws IOException { checkNotClosed(); validateKey(key); //假设lruEntries中没有该Entry,直接翻译一个null Entry entry = lruEntries.get(key); if (entry ==null) { returnnull; } //假设该Entry还没有公布。那么也返回一个null if (!entry.readable) { returnnull; } // Open all streams eagerly to guarantee that we see a single published // snapshot. If we opened streams lazily then the streams could come // from different edits. //循环遍历Entry中的每个file,以及每个file所代表的输入流 File[] files = new File[valueCount]; InputStream[] ins = new InputStream[valueCount]; try { File file; for (int i = 0; i <valueCount; i++) { file = entry.getCleanFile(i); files[i] = file; ins[i] = new FileInputStream(file); } } catch (FileNotFoundException e) { // A file must have been deleted manually! for (int i = 0; i <valueCount; i++) { if (ins[i] !=null) { Util.closeQuietly(ins[i]); } else { break; } } returnnull; } redundantOpCount++; //标记哪一个文件正在读取 journalWriter.append(READ +' ' + key + ' '); if (journalRebuildRequired()) { executorService.submit(cleanupCallable); } returnnew Snapshot(key, entry.sequenceNumber, files, ins, entry.lengths); }
此时向日志中加READ 记录
通过代码能够发现,当向缓存中取数据的时候须要检測是否重建日志。详细怎么重建,见下文。在此暂不做描写叙述。
从缓存中删除数据
调用DiscCacheAware接口的remove(String imageUri方法)来实现。详细的删除的主要逻辑:
删除缓存中相应的文件。向日志中追加REMOVE行,从lruEntires中删除相应的entry
关闭缓存,清空缓存
通过close方法来实现,详细的逻辑为:
对正在编辑的entry进行撤销操作;
调用trimToSize使得已经使用缓存的大小不超过maxSize
调用trimToFileCount()方法使得缓存中的文件方法小于maxfileCount
关闭日志journalWriter
清空缓存的clear除了以上的逻辑外还对directory进行了删除操作。
--------------------------------------------------------------------------------------------------------------
LruDiscCache,看到这个类的名字就是到该类用到了近期最久未使用(LRU)算法来处理文件缓存。
该算法的在该类核心思想的体现就是选择在近期一段时间里最久没有使用过的缓存文件删除。
该类也提供了跟BasicDiscCache一样的默认属性,比方默认缓存的大小为32k,默认压缩后的图片格式为png等等。
另外也提供了后备缓存reserveCacheDir。只是跟BasicDiscCache不同的是BasicDiscCache中代表缓存文件夹的cacheDir在LruDiscCache中用DiskLruCache对象的引用cache来取代。
//用DiskLruCache来取代,在BasicDiscCache中用File cacheDir来表示 protected DiskLruCachecache; private FilereserveCacheDir; protectedfinal FileNameGeneratorfileNameGenerator;
LruDiscCache提供了一个主要构造函数。该构造函数里面的參数主要用来初始化fileNameGenerator和cache对象。在此构造器中能够设置最大缓存的大小,缓存最多能够保存多少条数据的參数,这些參数都是初始化cache对象所须要的数据。
DiscCache接口提供的方法在LruDiscCache中的核心实现都转移到了cache对象中(或者说是DiskLruCache中)。以下就说说DiskLruCache,然后在掉过头来说LruDiscCache。
该类也定义了一下变量:
//缓存图片的文件夹 privatefinal Filedirectory; //最大缓存的大小 privatelongmaxSize; //最多缓存多少文件 privateintmaxFileCount; //每个entry所封装的文件的数目 privatefinalintvalueCount; //缓存的大小 privatelongsize = 0; //缓存文件的数目 privateintfileCount = 0;
另外该类还封装了一个重要类型为LinkedHashMap的属性lruEntries,用它来实现LRU算法(近期最久未使用算法)
//最后一个參数设置为true表示訪问的顺序 privatefinalLinkedHashMap<String, Entry>lruEntries = newLinkedHashMap<String, Entry>(0, 0.75f,true)
用LinkedHashMap能实现LRU算法的原因是在迭代map遍历列表中的元素时近期訪问的元素会排在LinkedHashMap的尾部 。
在这里简介一个样例作为说明:假设一个LinkedHashMap中通过一个for循环增加了a b c d e 五个元素,然后调用get方法获取元素a,那么当再次遍历该map的时候iterator.next().getValue()会依次输入b c d e a而不是a b c d e,这样通过近期常常使用的元素(比方get方法获取的元素a)就放在后面。近期最少使用的就排在了链表的前面,从而实现了LRU算法。
最后另一个long 类型的nextSequenceNumber:为了对新的和旧的快照做区分,每个entry对象在每一次编辑被提交的时候会获取一个序列号(nextSequenceNumber),假设这个序列号不等于entry的序列号的话,就说明该快照是旧的快照。
(近期最久未使用的快照)
在这个缓存中实现对近期最久未使用文件的删除的目的和时机例如以下:
目的:
1) 在缓存文件总大小超出最大缓存大小maxSize时对近期最久未使用的图片缓存进行删除,核心方法为:trimToSize
2) 在缓存中的文件总数目超过缓存要求的最大文件数目fileCount时对近期最久未使用的的图片缓存进行删除。核心方法为:trimToFileCount
这两个方法调用的时机从整体来说分为三个,因为这些操作涉及到IO操作,费时。所以在代码中交给了一个Callable去处理,详细的核心代码例如以下
final ThreadPoolExecutorexecutorService = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>()); //对缓存的清理操作 privatefinal Callable<Void>cleanupCallable =new Callable<Void>() { public Void call()throws Exception { synchronized (DiskLruCache.this) { //缓存已经关闭 if (journalWriter ==null) { returnnull;// Closed. } //删除缓存中处于非编辑状态的entry相应的文件,使得缓存大小I小于maxSize trimToSize(); //删除对于的文件,使得缓存中的文件数少于maxFileCount trimToFileCount(); //推断是否须要开启新的日志 if (journalRebuildRequired()) { rebuildJournal(); redundantOpCount = 0; } } returnnull; } };
运行Callable的三种时机:
1) 调用setMax方法设置缓存的最大值的时候
2) 成功缓存一个文件的时候,也就是说调用complete第二个參数为true的时候、
3) 要在删除缓存中的文件
版权声明:本文博客原创文章。博客,未经同意,不得转载。