Android-Universal-Image-Loader --LruDiscCach

在正式最近最久未使用緩存(LruDiscCache)以前,先介紹一個概念和重要的三個類: java

key:是DiscCacheAware接口中save方法裏面的imageUri參數經過調用FileNameGenerator的generate(imageUri)所生成的字符串,key必須知足[a-z0-9_-]{1,64};對應着Entry,Snapshot以及Editor的key字段。經過以以下方法來檢測key的合法性 算法

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. privatevoid validateKey(String key) {  
  2.         Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);  
  3.         if (!matcher.matches()) {  
  4.   
  5.             thrownew IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\"");  
  6.         }  
  7.       

Entry:是DiskLruCache裏面的內部類,每個key對應多個緩存圖片文件,保存文件的個數是由Entrylengths數組的長度來決定的,若是追根就地的話是valueCount來決定Entry中封裝的文件的個數。(不過在實際初始化緩存的過程當中,該每個Entry中只有一個圖片緩存文件與之對應,也就是說一個key對應一個file 數組

該類只有一個構造函數用來初始化key和lengths數組 緩存

屬性名稱 app

說明 less

類型 編輯器

key 函數

每個key對應多個file,該能夠是圖片文件 ui

String this

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

 

…………….

\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類中與日誌相關的字段以下所示:

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1.    
  2.   
  3. staticfinal String JOURNAL_FILE = "journal";  
  4.   
  5.     staticfinal String JOURNAL_FILE_TEMP = "journal.tmp";  
  6.   
  7.     staticfinal String JOURNAL_FILE_BACKUP = "journal.bkp";  
  8.   
  9.     staticfinal String MAGIC = "libcore.io.DiskLruCache";  
  10.   
  11.     staticfinal String VERSION_1 = "1";  
  12.   
  13.     staticfinallongANY_SEQUENCE_NUMBER = -1;  
  14.   
  15.     staticfinal Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,64}");  
  16.   
  17.     privatestaticfinal String CLEAN = "CLEAN";  
  18.   
  19.     privatestaticfinal String DIRTY = "DIRTY";  
  20.   
  21.     privatestaticfinal String REMOVE = "REMOVE";  
  22.   
  23.     privatestaticfinal String READ = "READ";  
  24.   
  25.     privatefinal File journalFile;  
  26.   
  27.     //調用rebuildJorunal從新建立日誌文件的時候會把日誌信息寫入到該文件中去  
  28.   
  29.     privatefinal File journalFileTmp;  
  30.   
  31.     privatefinal File journalFileBackup;  
  32.   
  33.     privatefinalintappVersion;  
  34.   
  35. private Writer journalWriter;  
  36.   
  37. private int reduantOpCount;//用來判斷是否重建日誌的字段  
  38.   
  39. private int redundantOpCount;  


注意:其中的journalWriter,該對象用來向日志文件中寫入數據,同時該對象是否爲null是做爲緩存是否關閉的決定條件:下面三個方法能夠說明這個結論

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. //檢測緩存是否關閉  
  2. publicsynchronizedboolean isClosed() {  
  3.         returnjournalWriter ==null;  
  4.     }  
  5.     //檢測緩存是否未關閉  
  6.     privatevoid checkNotClosed() {  
  7.         if (journalWriter ==null) {  
  8.             thrownew IllegalStateException("cache is closed");  
  9.         }  
  10.     }  
  11.   
  12.    /** 
  13.      * 關閉緩存,journalWirter爲空的話,說明緩存已經關閉:方法調用結束 
  14.      * 不然的話循環遍歷lruEntries中的每個Entry,撤掉正在編輯的Entry。 
  15.      * Closes this cache. Stored values will remain on thefilesystem. */  
  16.     publicsynchronizedvoid close()throws IOException {  
  17.         if (journalWriter ==null) {  
  18.             return;//緩存已經關閉,直接退出方法調用  
  19.         }  
  20.         //對處於編輯中的entry進行撤銷編輯操做  
  21.         for (Entry entry :new ArrayList<Entry>(lruEntries.values())) {  
  22.            if (entry.currentEditor !=null) {  
  23.               entry.currentEditor.abort();  
  24.             }  
  25.         }  
  26.         trimToSize();  
  27.         trimToFileCount();  
  28.         //關閉日誌輸出流  
  29.         journalWriter.close();  
  30.         journalWriter =null;  
  31.     }  


---------------------------------------------------------------------------------------------------

其實,這個類中大部分的方法都是再操做這些日誌文件,固然日誌文件的大小也有限制,而這個限制就是有redundantOpCount字段決定的,若是以下方法返回true的話就從新創建一個新的日誌文件,並把原來的日誌文件刪除掉

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. //判斷是否須要重建日誌文件當  
  2.   
  3. privatebooleanjournalRebuildRequired() {  
  4.         finalint redundantOpCompactThreshold = 2000;  
  5.        returnredundantOpCount >= redundantOpCompactThreshold  
  6.                 && redundantOpCount >= lruEntries.size();  
  7.     }  

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.邏輯代碼的處理以下:

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. DiskLruCache cache = new DiskLruCache();//第一次初始化  
  2. if(cache.journalFile.exists()){  
  3.   try{  
  4.       對日誌文件進行IO操做,具體操做的邏輯下文描述  
  5.       return cache;  
  6. }catch(IOException journalFileException){  
  7.    操做日誌出現IO錯誤  
  8.    cache.delete();刪除緩存  
  9. }  
  10. }  
  11. cache.makDir();  
  12. cache = new DiskLruCache();//第二次初始化  
  13. cache.rebuildJournalFile();  
  14. 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對象,(這是第一次添加):

附帶具體方法實現:

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. //讀入日誌的一行數據  
  2.    privatevoid readJournalLine(String line)throws IOException {  
  3.        //獲取第一個空格的位置  
  4.        int firstSpace = line.indexOf(' ');  
  5.        int keyBegin = firstSpace + 1;  
  6.        //獲取第二個空格的位置  
  7.       int secondSpace = line.indexOf(' ', keyBegin);  
  8.        //獲取該行數據表明的所表明的key  
  9.        final String key;  
  10.        if (secondSpace == -1) {//若是第二個空格不存在            
  11.            key = line.substring(keyBegin);  
  12.    //若是改行數據是以REMOVE開頭的狀況下,就從lruEntries中刪除該key鎖表明的entry  
  13.            if (firstSpace ==REMOVE.length() && line.startsWith(REMOVE)) {  
  14.                lruEntries.remove(key);  
  15.                return;  
  16.            }  
  17.        } else {  
  18.            key = line.substring(keyBegin, secondSpace);  
  19.        }  
  20.        //獲得日誌文件當前行鎖記錄的key  
  21.        Entry entry = lruEntries.get(key);  
  22.        if (entry ==null) {  
  23.            entry = new Entry(key);  
  24.            //若是沒有就建立一個Entry,並放入lruEntries中  
  25.            lruEntries.put(key, entry);  
  26.        }  
  27.        //若是改行數據已ClEAN開頭  
  28.        if (secondSpace != -1 && firstSpace ==CLEAN.length() && line.startsWith(CLEAN)) {  
  29.           String[] parts = line.substring(secondSpace + 1).split(" ");  
  30.            entry.readable =true;//設置給Entry爲發佈狀態  
  31.            entry.currentEditor =null;//編輯對象設置爲空  
  32.            entry.setLengths(parts);//設置該entry中每個file的大小  
  33.        } //若是該行數據已DIRTY開頭,那麼設置key所對應的entry爲編輯狀態  
  34.        elseif (secondSpace == -1 && firstSpace ==DIRTY.length() && line.startsWith(DIRTY)) {//若是該條數據是以DIRTY開頭,就將該entry設置爲編輯狀態  
  35.            entry.currentEditor =new Editor(entry);  
  36.        } elseif (secondSpace == -1 && firstSpace ==READ.length() && line.startsWith(READ)) {  
  37.            // This work was already done by calling lruEntries.get().  
  38.        } else {  
  39.            thrownew IOException("unexpected journal line: " + line);  
  40.        }  
  41.    }  

2) 調用processJournal()對日誌文件進一步處理:遍歷lruEntries(lruEntires中的數據在步驟1中的readJournalLine方法中添加的)中的每個Entry對象,對同編輯狀態的Entry進行不一樣的處理;對於處於非編輯狀態的entry。也就是entry.currentEditor==null的entry,計算他們的總的文件數目fileCount以及總的文件的大小size;而entry.currentEditor!=null的Entry(注意是由日誌文件中的DIRTY對應的Entry),這些,刪除這些entry對應的每個file,也便是說直接從緩存中刪除了這些緩存文件。

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. privatevoidprocessJournal()throws IOException {  
  2.         deleteIfExists(journalFileTmp);  
  3.         for (Iterator<Entry> i =lruEntries.values().iterator(); i.hasNext(); ) {  
  4.             Entry entry = i.next();  
  5.             if (entry.currentEditor ==null) {  
  6.                 for (int t = 0; t <valueCount; t++) {  
  7.                     size += entry.lengths[t];  
  8.                     fileCount++;  
  9.                 }  
  10.             } else {  
  11.                 entry.currentEditor =null;  
  12.                 for (int t = 0; t <valueCount; t++) {  
  13.                     deleteIfExists(entry.getCleanFile(t));  
  14.                     deleteIfExists(entry.getDirtyFile(t));  
  15.                 }  
  16.                 i.remove();  
  17.             }  
  18.         }  
  19.     }  

3) 初始化journalWriter

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. ache.journalWriter =new BufferedWriter(  
  2.                         new OutputStreamWriter(new FileOutputStream(cache.journalFile,true), Util.US_ASCII))  

到此完成了對緩存的初始化操做,注意這是在讀取日誌文件時沒有拋出異常的時候完成的初始化,若是拋出異常的話,就再次進行以下初始化

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. directory.mkdirs();  
  2. cache = new DiskLruCache(directory, appVersion, valueCount, maxSize, maxFileCount);  
  3. cache.rebuildJournal();//把原來的日誌文件刪除,並從新建立一個寫入前五行數據的日誌文件,以及從新建立journalWirt  
到如今最終的緩存初始化操做纔算真正的完成! 

數據寫入緩存

   向緩存寫入數據時經過DiscCacheWare的兩個save方法來實現的:核心思想是從lruEntries中獲取指定key(假設該key123456對應的Entry對象(若是沒有就往lruEntries中添加),而後獲取該Entry對象的Editor對象editor,並返回editor。由於這個editor包含了文件的輸出流,用該輸出流來想緩存中寫入數據,從而達到緩存圖片的目的。(注意此時向journal文件中寫入了DIRTY記錄)>

此時lruEntries中和journal文件中的包含的數據以下:

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. DiskLruCache.Editor editor = cache.edit(getKey(imageUri));//這個editor是一個新的編輯器對象  
  2. privatesynchronized Editor edit(String key,long expectedSequenceNumber)throws IOException {  
  3.         //檢測緩存是否關閉  
  4.         checkNotClosed();  
  5.         //檢測key是否符合規則  
  6.         validateKey(key);  
  7.         //從lruEntries中獲取指定對象的key  
  8.         Entry entry = lruEntries.get(key);  
  9.         if (expectedSequenceNumber !=ANY_SEQUENCE_NUMBER && (entry ==null  
  10.                 || entry.sequenceNumber != expectedSequenceNumber)) {  
  11.             returnnull;//快照已經陳舊  
  12.         }  
  13.         //若是在lruEntries中沒有對應的entry對象,則建立並添加  
  14.         if (entry ==null) {  
  15.             entry = new Entry(key);  
  16.             lruEntries.put(key, entry);  
  17.         }//若是Entry存在而且還處於編輯狀態的話就返回一個null  
  18.         elseif (entry.currentEditor != null) {  
  19.             returnnull;// Another edit is in progress.  
  20.         }  
  21.        //建立一個新的編輯器對象  
  22.         Editor editor = new Editor(entry);  
  23.         entry.currentEditor = editor;  
  24.         // Flush the journal before creating files to prevent file leaks.  
  25.         //建立新的文件以前刷新Writer以阻止文件泄露  
  26.         journalWriter.write(DIRTY +' ' + key + '\n');  
  27.         journalWriter.flush();  
  28.        return editor;  
  29.     }  
  寫入緩存成功後調用editor.commit()來完成保存數據的操做, (注意此時向 journal 文件中寫入了 CLEAN 記錄)

若是失敗的話調用editor.abort()來撤掉這次的編輯,同時從lruEntries中刪除此entry.(注意此時向journal文件中寫入了REMOVE記錄)

從緩存中取數據:

從緩存中取數據是經過調用DiscCacheWare的get(String imageUri)方法來實現的。前面說過,從緩存中取數據的時候獲取的其實是一個Entry的Snapshot,具體的方法以下:

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. publicsynchronized Snapshot get(String key)throws IOException {  
  2.         checkNotClosed();  
  3.         validateKey(key);     
  4.         //若是lruEntries中沒有該Entry,直接翻譯一個null  
  5.         Entry entry = lruEntries.get(key);  
  6.         if (entry ==null) {  
  7.             returnnull;  
  8.         }  
  9.        //若是該Entry尚未發佈,那麼也返回一個null  
  10.         if (!entry.readable) {  
  11.             returnnull;  
  12.         }  
  13.         // Open all streams eagerly to guarantee that we see a single published  
  14.         // snapshot. If we opened streams lazily then the streams could come  
  15.         // from different edits.  
  16.        //循環遍歷Entry中的每個file,以及每個file所表明的輸入流  
  17.         File[] files = new File[valueCount];  
  18.         InputStream[] ins = new InputStream[valueCount];  
  19.         try {  
  20.             File file;  
  21.            for (int i = 0; i <valueCount; i++) {  
  22.                 file = entry.getCleanFile(i);  
  23.                 files[i] = file;  
  24.                 ins[i] = new FileInputStream(file);  
  25.             }  
  26.         } catch (FileNotFoundException e) {  
  27.             // A file must have been deleted manually!  
  28.             for (int i = 0; i <valueCount; i++) {  
  29.                 if (ins[i] !=null) {  
  30.                    Util.closeQuietly(ins[i]);  
  31.                 } else {  
  32.                     break;  
  33.                }  
  34.             }  
  35.             returnnull;  
  36.         }  
  37.         redundantOpCount++;  
  38.         //標記哪個文件正在讀取  
  39.         journalWriter.append(READ +' ' + key + '\n');  
  40.         if (journalRebuildRequired()) {  
  41.             executorService.submit(cleanupCallable);  
  42.         }  
  43.         returnnew Snapshot(key, entry.sequenceNumber, files, ins, entry.lengths);  
  44.     }  

此時向日志中加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來代替。

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. //用DiskLruCache來代替,在BasicDiscCache中用File cacheDir來表示  
  2.   protected DiskLruCachecache;  
  3.   private FilereserveCacheDir;  
  4.   protectedfinal FileNameGeneratorfileNameGenerator;  

LruDiscCache提供了一個主要構造函數,該構造函數裏面的參數主要用來初始化fileNameGenerator和cache對象。在此構造器中能夠設置最大緩存的大小,緩存最多能夠保存多少條數據的參數,這些參數都是初始化cache對象所須要的數據。

DiscCache接口提供的方法在LruDiscCache中的核心實現都轉移到了cache對象中(或者說是DiskLruCache中)。下面就說說DiskLruCache,而後在掉過頭來講LruDiscCache。

該類也定義了一下變量:

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. //緩存圖片的目錄  
  2.     privatefinal Filedirectory;  
  3.     //最大緩存的大小  
  4.     privatelongmaxSize;  
  5.     //最多緩存多少文件  
  6.     privateintmaxFileCount;  
  7.     //每個entry所封裝的文件的數目  
  8.     privatefinalintvalueCount;  
  9.     //緩存的大小  
  10.     privatelongsize = 0;  
  11.     //緩存文件的數目  
  12.     privateintfileCount = 0;  

另外該類還封裝了一個重要類型爲LinkedHashMap的屬性lruEntries,用它來實現LRU算法(最近最久未使用算法)

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. //最後一個參數設置爲true表示訪問的順序  
  2. privatefinalLinkedHashMap<String, Entry>lruEntries =  
  3.             newLinkedHashMap<String, Entry>(00.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去處理,具體的核心代碼以下

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. final ThreadPoolExecutorexecutorService =  
  2.             new ThreadPoolExecutor(01, 60L, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());  
  3.     //對緩存的清理操做  
  4.     privatefinal Callable<Void>cleanupCallable =new Callable<Void>() {  
  5.         public Void call()throws Exception {  
  6.             synchronized (DiskLruCache.this) {  
  7.                 //緩存已經關閉  
  8.                 if (journalWriter ==null) {  
  9.                     returnnull;// Closed.  
  10.                 }  
  11.                //刪除緩存中處於非編輯狀態的entry對應的文件,使得緩存大小I小於maxSize  
  12.                 trimToSize();  
  13.                 //刪除對於的文件,使得緩存中的文件數少於maxFileCount  
  14.                 trimToFileCount();  
  15.                 //判斷是否須要開啓新的日誌  
  16.                 if (journalRebuildRequired()) {  
  17.                     rebuildJournal();  
  18.                     redundantOpCount = 0;  
  19.                 }  
  20.             }  
  21.             returnnull;  
  22.         }  
  23.     };   

 執行Callable的三種時機:

1) 調用setMax方法設置緩存的最大值的時候

2)  成功緩存一個文件的時候,也就是說調用complete第二個參數爲true的時候、

3)  對緩存中的文件進行刪除的時候 

相關文章
相關標籤/搜索