Android-Universal-Image-Loader学习笔记(二)--LruDiscCache

在正式最近最久未使用缓存(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的日志文件,典型的日志文件看清来如下格式,用日志文件来决定LRU算法

每个日志文件的开头前面五行数据分别为


行号


该行的数据


1


libcore.io.DiskLruCache


2


该缓存的版本号  例如1


3


app的版本号    例如100


4


每一个Entry中所对应的File的数目例如2


5


空白行

第五行过后就是日志的正文,日志正文的格式如下

CLEAN行所代表的数据格式如下


CLEAN


entry所代表的key


f1.length


f2.length


…………….


\n

特别说明:f1.length 是key所对应的entry中第一个文件的大小,和f2.length之间用一个空格隔开,具体fn是多少由日志中第四行的数据所决定。如果追根究底的话,是由Entry对象中的lengths数组的长度来决定或者是DiskLruCache的valueCount字段来决定(因为lengths数组的长度初始化的时候就是valueCount)。

REMOVE行READ行以及DIRTY行显示的数据格式较为简单:


REMOVE


entry对象的key


\n


READ


同上


\n


DIRTY


同上


\n

下面是源代码中给出的日志样本文件,如图

每行字段的被写入日志文件的时机如下表:


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;


cache = new DiskLruCache(File directory,int appVersion,int
valueCount,long maxSize,int 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


cache.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();//把原来的日志文件删除,并重新创建一个写入前五行数据的日志文件,以及重新创建journalWirter

到现在最终的缓存初始化操作才算真正的完成!

数据写入缓存

向缓存写入数据时通过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 + ‘\n‘);

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 + ‘\n‘);

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)  对缓存中的文件进行删除的时候

Android-Universal-Image-Loader学习笔记(二)--LruDiscCache

时间: 2024-11-09 00:34:37

Android-Universal-Image-Loader学习笔记(二)--LruDiscCache的相关文章

Android第一行代码学习笔记二---在活动中使用Toast

Toast:是Android系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间. 首先需要定义一个弹出Toast触发点,接着笔记一的程序,正好上面有个按钮,我们就点击这个按钮的时候弹出来一个Toast,在onCreate()方法中添加如下代码: protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceStat

Android学习笔记二十九之SwipeRefreshLayout、RecyclerView和CardView

Android学习笔记二十九之SwipeRefreshLayout.RecyclerView和CardView 前面我们介绍了AlertDialog和几个常用的Dialog,ProgressDialog进度条提示框.DatePickerDialog日期选择对话框和TimePickerDialog时间选择对话框.这一节我们介绍几个新的API控件SwipeRefreshLayout.RecyclerView和CardView,这几个API控件都是google在Android5.0推出的.下面我们来学

Android学习笔记二

17. 在ContentProvider中定义的getType()方法是定义URI的内容类型. 18. SQLiteDatabase类中的insert/delete/update/query方法其实也挺好用的,我在EquipmentProvider类中做了实现 19. Android专门有个单元测试项目(Android Test Project),在这个项目中,可以新建一个继承AndroidTestCase类的具体测试类来单元测试某个功能.我新建了一个AndroidTestProject项目,在

Android学习笔记二十之Toast吐司、Notification通知、PopupWindow弹出窗

Android学习笔记二十之Toast吐司.Notification通知.PopupWindow弹出窗 Toast吐司 Toast吐司是我们经常用到的一个控件,Toast是AndroidOS用来显示消息的一种机制,它与Dialog不同,Toast不会获取到焦点,通常显示一段时间之后就会自动消失,下面我们来介绍Toast的几种常用方式: 第一种,默认显示方式,也是最常用的方式: Toast.makeText(MainActivity.this, "这是默认的显示方式", Toast.LE

Android学习笔记二十五之ListView多布局实现

Android学习笔记二十五之ListView多布局实现 这一节是介绍ListView这个控件的最后一节,实现一个Item的多布局.像我们经常在用的各种即时通讯工具,QQ.微信等,假设他们的会话界面是ListView实现的,那么ListView就有多种Item布局,这一节,我们就来实现一个ListView的多种Item. 要实现ListView里面有多种Item,就要重写适配器的两个方法getViewTypeCount()和getItemViewType(int position),第一个方法是

Android学习笔记二十四之ListView列表视图二

Android学习笔记二十四之ListView列表视图二 前面一篇我们介绍了常用的几种适配器的简单实现和ListView的简单使用,这一篇中,我们介绍一下ListView的优化和一些其它的问题. ListView优化方法一 在ListView中,我们最常用的就是自定义Adapter,在我们自定义Adapter中,需要实现两个比较重要的方法getCount()和getView(),前者是负责计算ListView的总Item数,后者是生成Item,有多少个Item就会调用getView()方法多少次

Android学习笔记二十七之ExpandableListView可折叠列表和StackView栈视图

Android学习笔记二十七之ExpandableListView可折叠列表和StackView栈视图 ExpandableListView可折叠列表 这一节我们介绍第三个用适配器的控件,ExpandableListView可折叠列表.这个控件可以实现我们在QQ中非常常见好友分组功能,ExpandableListView是ListView的子类,用法跟ListView差不多,下面我们来学习这个控件的基本使用: 常用属性: android:childDivider:指定各组内子类表项之间的分隔条,

Android学习笔记二-Linear Layout

1.LinearLayout是子view均为单方向的,即均为水平或垂直方向的布局.你可以用android:orientation属性来 定义layout方向 所有子view都是一个挨着一个的,所以一个垂直列表,不管它本身多宽,同时只能有一行.若是水平列表,则都等高. 2.LayoutWeight 用来操控各个子view的相对比例,,即各个子元素对空间的使用权重 Weight并非网上很多文章所叙述的那样(文章都过于片面),weight是指某个组件在布局中[剩余空间]中的显示权重,那么所谓的剩余空间

Cookie学习笔记二:Cookie实例

今天说说刚刚学到的两个Cookie的最经典应用:自动登录和购物车设置 一:自动登录 需要两个页面:login.jsp与index.jsp,login.jsp用来输出登录信息,index.jsp处理登录信息:如果有Cookie,则自动登录,否则创建输入信息的对象的Cookie,下次登录可以直接登录,但是我们在这里给Cookie设置一个最大保存时间30s,即登录30s后会自动退回到登陆页面,具体代码如下: login.jsp: <%@ page language="java" con

Android(java)学习笔记233: 远程服务的应用场景(移动支付案例)

一. 移动支付:       用户需要在移动终端提交账号.密码以及金额等数据 到 远端服务器.然后远端服务器匹配这些信息,进行逻辑判断,进而完成交易,返回交易成功或失败的信息给移动终端.用户提交账号.密码以及金额等数据都是比较敏感的数据,这些数据不能让外界获取.       阿里等等支付宝平台把支付的逻辑封装起来,只给我们提供一个方法去调用,这样提高了安全性.当我们用户提交账号.密码以及金额等数据,点击"支付"的时候,支付宝平台已经调用方法加密数据(这个支付逻辑是远程服务,为了安全,防