DiskLruCache分析

DiskLruCache是Android提供的一个管理磁盘缓存的类。该类可用于在程序中把从网络加载的数据
保存到磁盘上作为缓存数据,例如一个显示网络图片的gridView,可对从网络加载的图片进行缓存,
提高程序的可用性。

一.文件

例如对于一组从网络加载的图片进行缓存,则在DiskLruCache的工作目录下面,可以看到如下所示
的文件:
5TD379N6L1_2RH%(]BJU588.jpg 
前8行是对八张图片的缓存文件,第9行的文件叫journal,它相当于该缓存的日志文件,改文件的格
式如下所示:

libcore.io.DiskLruCache
1
100
2

CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
DIRTY 335c4c6028171cfddfbaae1a9c313c52
CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
REMOVE 335c4c6028171cfddfbaae1a9c313c52
DIRTY 1ab96a171faeeee38496d8b330771a7a
CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
READ 335c4c6028171cfddfbaae1a9c313c52
READ 3400330d1dfc7f3f7f4b8d4d803dfcf6

前5行分别为一个常量字符串:libcore.io.DiskLruCache,该DiskLruCache的版本,应用程序的版本,
每个条目中保存值的个数,以及一个空行。

该文件中,随后记录的都是一个entry的状态。每行包括下面几项内容:一个状态,一个key,和可
选择的特定状态的值。
DIRTY:追踪那些活跃的条目,它们目前正在被创建或更新。每一个成功的DIRTY行后应该跟随一个
       CLEAN或REMOVE行。DIRTY行如果没有一个CLEAN或REMOVE行与它匹配,表明那是一个临时
       文件应该被删除。
CLEAN:跟踪一个发布成功的entry,并可以读取。一个发布行后跟着其文件的长度。
READ:跟踪对LRU的访问。
REMOVE:跟踪被删除的entry。

二.变量

下面是DiskLruCache中的一些静态常量,可从定义明白其意思:

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
static final String JOURNAL_FILE = "journal" ;
    static final String JOURNAL_FILE_TMP = "journal.tmp" ;
    static final String MAGIC = "libcore.io.DiskLruCache" ;
    static final String VERSION_1 = "1" ;
    static final long ANY_SEQUENCE_NUMBER = - 1 ;
    private static final String CLEAN = "CLEAN" ;
    private static final String DIRTY = "DIRTY" ;
    private static final String REMOVE = "REMOVE" ;
    private static final String READ = "READ" ;
 
    private static final Charset UTF_8 = Charset.forName( "UTF-8" );
    private static final int IO_BUFFER_SIZE = 8 * 1024 ;


下面是DiskLruCache中定义的一些变量:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
private final File directory;
    private final File journalFile;
    private final File journalFileTmp;
    private final int appVersion;
    private final long maxSize;
    private final int valueCount;
    private long size = 0 ;
    private Writer journalWriter;
    private final LinkedHashMap<String, Entry> lruEntries
             = new LinkedHashMap<String, Entry>( 0 , 0 .75f, true );
    private int redundantOpCount;
private long nextSequenceNumber = 0 ;


directory:指向该DiskLruCache的工作目录。
journalFile:指向journal文件。
journalFileTmp:当构建一个journal文件时,先会生成一个journalTmp文件,当文件构建完成时,
      会将该journalTmp重命名为journal,这是一个临时文件。
maxSize:是该DiskLruCache所允许的最大缓存空间。
valueCount:每个entry对应的缓存文件的格式,一般情况下,该值为1.
size:DiskLruCache当前缓存的大小。
journalWriter:指向journal文件,主要向该文件中写内容。
lruEntries:当前entry的列表。
redundantOpCount:当前journal文件中entry状态记录的个数,主要用来当该值大于一定限制时,
      对journal文件进行清理。
nextSequenceNumber:新旧entry的特征值。

三.内部类

1.Entry:该类表示DiskLruCache中每一个条目
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
private final class Entry {
         private final String key;
 
         /** 该entry中每个文件的长度,该数组长度为valueCount */
         private final long [] lengths;
 
         /** 该entry曾经被发布过,该项为true */
         private boolean readable;
 
         /** 该entry所对应的editor */
         private Editor currentEditor;
 
         /** 最近编辑这个entry的序列号 */
         private long sequenceNumber;
 
         private Entry(String key) {
             this .key = key;
             this .lengths = new long [valueCount];
         }
 
         public String getLengths() throws IOException {
             StringBuilder result = new StringBuilder();
             for ( long size : lengths) {
                result.append( ' ' ).append(size);
             }
             return result.toString();
         }
 
         private void setLengths(String[] strings) throws IOException {
             if (strings.length != valueCount) {
                throw invalidLengths(strings);
             }
 
             try {
                for ( int i = 0 ; i < strings.length; i++) {
                     lengths<i> = Long.parseLong(strings);
               </i>  }
             } catch (NumberFormatException e) {
                throw invalidLengths(strings);
             }
         }
 
         private IOException invalidLengths(String[] strings) throws IOException {
             throw new IOException( "unexpected journal line: " + Arrays.toString(strings));
         }
 
         public File getCleanFile( int i) {
             return new File(directory, key + "." + i);
         }
 
         public File getDirtyFile( int i) {
             return new File(directory, key + "." + i + ".tmp" );
         }
    }


2.Editor:该类控制对每一个entry的读写操作。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public final class Editor {
         private final Entry entry;
         private boolean hasErrors;
 
         private Editor(Entry entry) {
             this .entry = entry;
         }
 
         public InputStream newInputStream( int index) throws IOException {
             synchronized (DiskLruCache. this ) {
                if (entry.currentEditor != this ) {
                     throw new IllegalStateException();
                }
                if (!entry.readable) {
                     return null ;
                }
                return new FileInputStream(entry.getCleanFile(index));
             }
         }
 
         public String getString( int index) throws IOException {
             InputStream in = newInputStream(index);
             return in != null ? inputStreamToString(in) : null ;
         }
 
         /**
          * 返回该editor的key对应的文件的outputstream,文件名为key.index.tmp
          */
         public OutputStream newOutputStream( int index) throws IOException {
             synchronized (DiskLruCache. this ) {
                if (entry.currentEditor != this ) {
                     throw new IllegalStateException();
                }
                return new FaultHidingOutputStream( new FileOutputStream(entry.getDirtyFile(index)));
             }
         }
 
         public void set( int index, String value) throws IOException {
             Writer writer = null ;
             try {
                writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
                writer.write(value);
             } finally {
                closeQuietly(writer);
             }
         }
 
         public void commit() throws IOException {
             if (hasErrors) {
                completeEdit( this , false );
                remove(entry.key); // the previous entry is stale
             } else {
                completeEdit( this , true );
             }
         }
 
         public void abort() throws IOException {
             completeEdit( this , false );
         }
 
         private class FaultHidingOutputStream extends FilterOutputStream {
             private FaultHidingOutputStream(OutputStream out) {
                super (out);
             }
 
             @Override public void write( int oneByte) {
                try {
                     out.write(oneByte);
                } catch (IOException e) {
                     hasErrors = true ;
                }
             }
 
             @Override public void write( byte [] buffer, int offset, int length) {
                try {
                     out.write(buffer, offset, length);
                } catch (IOException e) {
                     hasErrors = true ;
                }
             }
 
             @Override public void close() {
                try {
                     out.close();
                } catch (IOException e) {
                     hasErrors = true ;
                }
             }
 
             @Override public void flush() {
                try {
                     out.flush();
                } catch (IOException e) {
                     hasErrors = true ;
                }
             }
         }
    }


3.Snapshot:该类表示DiskLruCache中每一个entry中缓存文件的快照,它持有该entry中每个文件的
        inputStream,通过该inputStream可读取该文件的内容。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public final class Snapshot implements Closeable {
         private final String key;
         private final long sequenceNumber;
         private final InputStream[] ins;
 
         private Snapshot(String key, long sequenceNumber, InputStream[] ins) {
             this .key = key;
             this .sequenceNumber = sequenceNumber;
             this .ins = ins;
         }
 
         public Editor edit() throws IOException {
             return DiskLruCache. this .edit(key, sequenceNumber);
         }
 
         public InputStream getInputStream( int index) {
             return ins[index];
         }
 
         public String getString( int index) throws IOException {
             return inputStreamToString(getInputStream(index));
         }
 
         @Override public void close() {
             for (InputStream in : ins) {
                closeQuietly(in);
             }
         }
    }


四.工作流程

1.初始化DiskLruCache
初始化DiskLruCache时,会调用该类中的一个静态函数:open
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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" );
         }
 
         // prefer to pick up where we left off
         DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
         if (cache.journalFile.exists()) {
             try {
                cache.readJournal();
                cache.processJournal();
                cache.journalWriter = new BufferedWriter( new FileWriter(cache.journalFile, true ),
                         IO_BUFFER_SIZE);
                return cache;
             } catch (IOException journalIsCorrupt) {
//                System.logW("DiskLruCache " + directory + " is corrupt: "
//                        + journalIsCorrupt.getMessage() + ", removing");
                cache.delete();
             }
         }
 
         // create a new empty cache
         directory.mkdirs();
         cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
         cache.rebuildJournal();
         return cache;
    }

该方法用于初始化一个DiskLruCache对象,在初始化过程中,如果之前已经创建过该缓存,则通过存在
的journal文件构建已有的entry列表(其中涉及的方法可在DisklruCache中进行查看),否则则创建一个
新的journal文件,调用rebuildJournal方法。

2.查找是否存在一个key所对应的缓存文件
当需要从缓存中查找一个缓存文件时,可进行如下的调用:
?
代码片段,双击复制
01
02
03
04
05
06
07
final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
                     if (snapshot != null ) {
                         inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
                         if (inputStream != null ) {
                            FileDescriptor fd = ((FileInputStream) inputStream).getFD();
                         }
                     }

该功能调用了DiskLruCache测get方法,来查找DiskLruCache中是否存在该key对应的Snapshot,若返回的
snapshot不为空,则可通过snapshot的inputstream来读取缓存文件的内容。若snapshot为空,表明该key
对应的文件不在缓存中。get方法如下:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public synchronized Snapshot get(String key) throws IOException {
         checkNotClosed();
         validateKey(key);
         Entry entry = lruEntries.get(key);
         if (entry == null ) {
             return null ;
         }
 
         if (!entry.readable) {
             return null ;
         }
 
         InputStream[] ins = new InputStream[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!
             return null ;
         }
       </i> Log.i( "chentest" , "redundantOpCount=" +redundantOpCount);
         redundantOpCount++;
         journalWriter.append(READ + ' ' + key + '\n' );
         if (journalRebuildRequired()) {
             executorService.submit(cleanupCallable);
         }
 
         return new Snapshot(key, entry.sequenceNumber, ins);
    }

该函数首先会检查该DiskLruCache是否关闭,以及key的合法性。然后从lruEntries中查找key所对应的
Entry,若entry不为空,则通过该entry获取缓存文件的路径,并封装到Snapshot中的inputstream中返
回该Snapshot。之后会对journal文件进行READ该key的操作,并检查是否要重新构建该journal。

3.向DiskLruCache中写入一条entry
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
                     if (snapshot == null ) {
                         final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                         if (editor != null ) {
                            out = editor.newOutputStream(DISK_CACHE_INDEX);
                            value.getBitmap().compress(
                                     mCacheParams.compressFormat, mCacheParams.compressQuality, out);
                            editor.commit();
                            out.close();
                         }
                     } else {
                         snapshot.getInputStream(DISK_CACHE_INDEX).close();
                     }

该功能首先会调用edit方法,返回一个Editor:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
         checkNotClosed();
         validateKey(key);
         Entry entry = lruEntries.get(key);
         if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
                && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
             return null ; // snapshot is stale
         }
         if (entry == null ) {
             entry = new Entry(key);
             lruEntries.put(key, 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(DIRTY + ' ' + key + '\n' );
         journalWriter.flush();
         return editor;
    }

该函数会针对该key创建一个entry,并将该entry加入lruEntries中,并创建一个相应的Editor,同
时在journal文件中加入一条对该key的DIRTY记录。

其次,上面的功能通过Editor返回一个该key所对应的文件的输出流,并将文件保存到输出流中。
最后,会调用edit的commit方法,而commit方法调用了DiskLruCache中的completeEdit方法:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
         Entry entry = editor.entry;
         if (entry.currentEditor != editor) {
             throw new IllegalStateException();
         }
 
         // if this edit is creating the entry for the first time, every index must have a value
         if (success && !entry.readable) {
             for ( int i = 0 ; i < valueCount; i++) {
                if (!entry.getDirtyFile(i).exists()) {
                     editor.abort();
                     throw new IllegalStateException( "edit didn't create file " + i);
                }
             }
         }
 
         for ( int i = 0 ; i < valueCount; i++) {
             File dirty = entry.getDirtyFile(i);
             if (success) {
                if (dirty.exists()) {
                     File clean = entry.getCleanFile(i);
                     dirty.renameTo(clean);
                     long oldLength = entry.lengths;
                     long newLength = clean.length();
                     entry.lengths = newLength;
                     size = size - oldLength + newLength;
                }
             } else {
                deleteIfExists(dirty);
             }
         }
 
         redundantOpCount++;
         entry.currentEditor = null ;
         if (entry.readable | success) {
             entry.readable = true ;
             journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n' );
             if (success) {
                entry.sequenceNumber = nextSequenceNumber++;
             }
         } else {
             lruEntries.remove(entry.key);
             journalWriter.write(REMOVE + ' ' + entry.key + '\n' );
         }
 
         if (size > maxSize || journalRebuildRequired()) {
             executorService.submit(cleanupCallable);
         }
    }

该函数主要会根据保存缓存文件的大小更新DiskLruCache的总大小,并且在journal文件对该key
加入CLEAN的log,最后会判断DiskLruCache的总大小是否超过maxSize,以便对缓存进行清理。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值