BlobCache算法和LruCache算法是android中的圖片緩存算法喻鳄。LruCache算法在日常開發(fā)中用得比較多虫溜,但BlobCache卻用得比較少稳衬,網(wǎng)上介紹的文章也是少得可憐瘟忱。
跟LruCache不一樣,BlobCache并不屬于android的util薪夕,BlobCache最開始使用的地方是谷歌的Gallery脚草,具體源碼可以查看:BlobCache
一赫悄、BlobCache框架
BlobCache會(huì)在本地保存三個(gè)文件imageCache.idx原献、imageCache.0、imageCache.1(后綴固定埂淮,但前綴名字可以自定義)姑隅。其中imageCache.idx是數(shù)據(jù)的索引文件,imageCache.0和imageCache.1是保存數(shù)據(jù)的文件倔撞。
BlobCache算法的核心就是將所有的緩存數(shù)據(jù)都保存在同一個(gè)data文件中(imageCache.0或imageCache.1)讲仰,記錄緩存數(shù)據(jù)的索引保存在imageCache.idx文件中,由于imageCache.idx文件內(nèi)存占用較小痪蝇,讀寫時(shí)會(huì)把整個(gè)imageCache.idx文件映射至內(nèi)存鄙陡,然后使用RandomAccessFile隨機(jī)讀取接口,像操作指針一樣控制index的偏移量讀寫data文件對(duì)應(yīng)位置的數(shù)據(jù)躏啰。由于緩存文件存儲(chǔ)在同一個(gè)文件下趁矾,緩存數(shù)據(jù)只能增加不能刪除,BlobCache巧妙通過(guò)兩個(gè)data文件(active和inactive即imageCache.0和imageCache.1)的翻轉(zhuǎn)來(lái)實(shí)現(xiàn)緩存數(shù)據(jù)的刪除更新给僵。
二毫捣、索引文件(imageCache.idx)
// index header offset
private static final int IH_MAGIC = 0;
private static final int IH_MAX_ENTRIES = 4;
private static final int IH_MAX_BYTES = 8;
private static final int IH_ACTIVE_REGION = 12;
private static final int IH_ACTIVE_ENTRIES = 16;
private static final int IH_ACTIVE_BYTES = 20;
private static final int IH_VERSION = 24;
private static final int IH_CHECKSUM = 28;
private static final int INDEX_HEADER_SIZE = 32;
// Appends the data to the active file. It also updates the hash entry.
// The proper hash entry (suitable for insertion or replacement) must be
// pointed by mSlotOffset.
private void insertInternal(long key, byte[] data, int length)
throws IOException {
byte[] header = mBlobHeader;
int sum = checkSum(data);
writeLong(header, BH_KEY, key);
writeInt(header, BH_CHECKSUM, sum);
writeInt(header, BH_OFFSET, mActiveBytes);
writeInt(header, BH_LENGTH, length);
mActiveDataFile.write(header);
mActiveDataFile.write(data, 0, length);
// key:8個(gè)字節(jié)
mIndexBuffer.putLong(mSlotOffset, key);
// offset 4個(gè)字節(jié)
mIndexBuffer.putInt(mSlotOffset + 8, mActiveBytes);
mActiveBytes += BLOB_HEADER_SIZE + length;
writeInt(mIndexHeader, IH_ACTIVE_BYTES, mActiveBytes);
}
private void resetCache(int maxEntries, int maxBytes) throws IOException {
mIndexFile.setLength(0); // truncate to zero the index
mIndexFile.setLength(INDEX_HEADER_SIZE + maxEntries * 12 * 2);
mIndexFile.seek(0);
byte[] buf = mIndexHeader;
// MAGIC 4個(gè)字節(jié)
writeInt(buf, IH_MAGIC, MAGIC_INDEX_FILE);
// MAX_ENTRIES 4個(gè)字節(jié)
writeInt(buf, IH_MAX_ENTRIES, maxEntries);
// MAX_BYTES 4個(gè)字節(jié)
writeInt(buf, IH_MAX_BYTES, maxBytes);
writeInt(buf, IH_ACTIVE_REGION, 0);
writeInt(buf, IH_ACTIVE_ENTRIES, 0);
writeInt(buf, IH_ACTIVE_BYTES, DATA_HEADER_SIZE);
writeInt(buf, IH_VERSION, mVersion);
writeInt(buf, IH_CHECKSUM, checkSum(buf, 0, IH_CHECKSUM));
mIndexFile.write(buf);
// This is only needed if setLength does not zero the extended part.
// writeZero(mIndexFile, maxEntries * 12 * 2);
mDataFile0.setLength(0);
mDataFile1.setLength(0);
mDataFile0.seek(0);
mDataFile1.seek(0);
writeInt(buf, 0, MAGIC_DATA_FILE);
mDataFile0.write(buf, 0, 4);
mDataFile1.write(buf, 0, 4);
}
BlobCache的索引文件(imageCache.idx)分成兩部分,前面32字節(jié)為用于記錄數(shù)據(jù)的頭部帝际,依次分別為:MAGIC蔓同、MAX_ENTRIES、MAX_BYTES蹲诀、ACTIVE_REGION斑粱、ACTIVE_ENTRIES、ACTIVE_BYTES脯爪、VERSION则北、CHECKSUM,都是占4個(gè)字節(jié)披粟;后面的是存儲(chǔ)圖片的key和該key對(duì)應(yīng)的圖片在數(shù)據(jù)文件中的起始位置咒锻,分別占8個(gè)字節(jié)和4個(gè)字節(jié),總共12個(gè)字節(jié)守屉。
三惑艇、數(shù)據(jù)文件(imageCache.0、imageCache.1)
private void resetCache(int maxEntries, int maxBytes) throws IOException {
mIndexFile.setLength(0); // truncate to zero the index
mIndexFile.setLength(INDEX_HEADER_SIZE + maxEntries * 12 * 2);
mIndexFile.seek(0);
byte[] buf = mIndexHeader;
。滨巴。思灌。。恭取。泰偿。。
蜈垮。耗跛。。攒发。调塌。。惠猿。
// This is only needed if setLength does not zero the extended part.
// writeZero(mIndexFile, maxEntries * 12 * 2);
mDataFile0.setLength(0);
mDataFile1.setLength(0);
mDataFile0.seek(0);
mDataFile1.seek(0);
writeInt(buf, 0, MAGIC_DATA_FILE);
// MAGIC 4個(gè)字節(jié)
mDataFile0.write(buf, 0, 4);
mDataFile1.write(buf, 0, 4);
}
// blob header offset
private static final int BH_KEY = 0;
private static final int BH_CHECKSUM = 8;
private static final int BH_OFFSET = 12;
private static final int BH_LENGTH = 16;
private static final int BLOB_HEADER_SIZE = 20;
BlobCache的數(shù)據(jù)文件(imageCache.0羔砾、imageCache.1)分成兩部分,前面部分是MAGIC偶妖,占4個(gè)字節(jié)姜凄;后面部分是圖片的所有數(shù)據(jù),包括Blob頭部和數(shù)據(jù)趾访。Blob頭部又包括KEY(8字節(jié))态秧、CHECKSUM(4字節(jié))、OFFSET(4字節(jié))腹缩、LENGTH(4字節(jié))屿聋,總共20字節(jié);數(shù)據(jù)是指圖片的數(shù)據(jù)藏鹊,其長(zhǎng)度是變化的润讥,在Blob頭部的LENGTH字段中存儲(chǔ)。
四盘寡、寫入圖片數(shù)據(jù)流程
需要預(yù)先生成圖片的key和data數(shù)據(jù)文件
// Inserts a (key, data) pair into the cache.
public void insert(long key, byte[] data) throws IOException {
if (DATA_HEADER_SIZE + BLOB_HEADER_SIZE + data.length > mMaxBytes) {
throw new RuntimeException("blob is too large!");
}
// 校驗(yàn)數(shù)據(jù)文件大小是否達(dá)到上限
if (mActiveBytes + BLOB_HEADER_SIZE + data.length > mMaxBytes
|| mActiveEntries * 2 >= mMaxEntries) {
// 翻轉(zhuǎn)兩個(gè)data文件(active和inactive即imageCache.0和imageCache.1)
flipRegion();
}
// 根據(jù)key楚殿,在索引文件中查找是否存在文件,如果存在竿痰,則獲取位置
if (!lookupInternal(key, mActiveHashStart)) {
// If we don't have an existing entry with the same key, increase
// the entry count.
mActiveEntries++;
writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
}
// 插入數(shù)據(jù)
insertInternal(key, data, data.length);
// 更新索引文件
updateIndexHeader();
}
流程圖:
五脆粥、讀取圖片數(shù)據(jù)流程
讀取圖片數(shù)據(jù),需要關(guān)鍵的key影涉。請(qǐng)求時(shí)变隔,需要傳入封裝好的LookupRequest,也可以只傳key蟹倾,使用內(nèi)部封裝的LookupRequest匣缘。LookupRequest封裝了key猖闪、buffer和length:
public static class LookupRequest {
public long key; // input: the key to find
public byte[] buffer; // input/output: the buffer to store the blob
public int length; // output: the length of the blob
}
讀取到相應(yīng)的圖片數(shù)據(jù)則返回true,否則返回false:
public boolean lookup(LookupRequest req) throws IOException {
// Look up in the active region first.
if (lookupInternal(req.key, mActiveHashStart)) {
if (getBlob(mActiveDataFile, mFileOffset, req)) {
return true;
}
}
// We want to copy the data from the inactive file to the active file
// if it's available. So we keep the offset of the hash entry so we can
// avoid looking it up again.
int insertOffset = mSlotOffset;
// Look up in the inactive region.
if (lookupInternal(req.key, mInactiveHashStart)) {
if (getBlob(mInactiveDataFile, mFileOffset, req)) {
// If we don't have enough space to insert this blob into
// the active file, just return it.
if (mActiveBytes + BLOB_HEADER_SIZE + req.length > mMaxBytes
|| mActiveEntries * 2 >= mMaxEntries) {
return true;
}
// Otherwise copy it over.
mSlotOffset = insertOffset;
try {
insertInternal(req.key, req.buffer, req.length);
mActiveEntries++;
writeInt(mIndexHeader, IH_ACTIVE_ENTRIES, mActiveEntries);
updateIndexHeader();
} catch (Throwable t) {
Log.e(TAG, "cannot copy over");
}
return true;
}
}
return false;
}
流程圖:
六肌厨、BlobCache培慌、DiskLruCache讀取時(shí)間對(duì)比
BlobCache與DiskLruCache的存儲(chǔ)和讀取速度對(duì)比
七、BlobCache在開發(fā)中的使用
這部分也放在下下一篇文章中