BlobCache算法詳解

BlobCache算法和LruCache算法是android中的圖片緩存算法。LruCache算法在日常開發(fā)中用得比較多,但BlobCache卻用得比較少,網(wǎng)上介紹的文章也是少得可憐。

跟LruCache不一樣,BlobCache并不屬于android的util,BlobCache最開始使用的地方是谷歌的Gallery,具體源碼可以查看:BlobCache

一、BlobCache框架

BlobCache.png

BlobCache會在本地保存三個文件imageCache.idx、imageCache.0imageCache.1(后綴固定,但前綴名字可以自定義)。其中imageCache.idx是數(shù)據(jù)的索引文件,imageCache.0imageCache.1是保存數(shù)據(jù)的文件。

BlobCache算法的核心就是將所有的緩存數(shù)據(jù)都保存在同一個data文件中(imageCache.0imageCache.1),記錄緩存數(shù)據(jù)的索引保存在imageCache.idx文件中,由于imageCache.idx文件內(nèi)存占用較小,讀寫時會把整個imageCache.idx文件映射至內(nèi)存,然后使用RandomAccessFile隨機讀取接口,像操作指針一樣控制index的偏移量讀寫data文件對應(yīng)位置的數(shù)據(jù)。由于緩存文件存儲在同一個文件下,緩存數(shù)據(jù)只能增加不能刪除,BlobCache巧妙通過兩個data文件(active和inactive即imageCache.0imageCache.1)的翻轉(zhuǎn)來實現(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個字節(jié)
        mIndexBuffer.putLong(mSlotOffset, key);
         // offset 4個字節(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個字節(jié)
        writeInt(buf, IH_MAGIC, MAGIC_INDEX_FILE);
        // MAX_ENTRIES 4個字節(jié)
        writeInt(buf, IH_MAX_ENTRIES, maxEntries);
        // MAX_BYTES 4個字節(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索引文件.png

BlobCache的索引文件(imageCache.idx)分成兩部分,前面32字節(jié)為用于記錄數(shù)據(jù)的頭部,依次分別為:MAGIC、MAX_ENTRIES、MAX_BYTES、ACTIVE_REGION、ACTIVE_ENTRIES、ACTIVE_BYTES、VERSION、CHECKSUM,都是占4個字節(jié);后面的是存儲圖片的key和該key對應(yīng)的圖片在數(shù)據(jù)文件中的起始位置,分別占8個字節(jié)和4個字節(jié),總共12個字節(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個字節(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ù)文件.png

BlobCache的數(shù)據(jù)文件(imageCache.0imageCache.1)分成兩部分,前面部分是MAGIC,占4個字節(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ù),其長度是變化的,在Blob頭部的LENGTH字段中存儲。

四、寫入圖片數(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!");
        }

        // 校驗數(shù)據(jù)文件大小是否達到上限
        if (mActiveBytes + BLOB_HEADER_SIZE + data.length > mMaxBytes
                || mActiveEntries * 2 >= mMaxEntries) {
            // 翻轉(zhuǎn)兩個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();
    }

流程圖:

BlobCache寫入數(shù)據(jù)流程圖.png

五、讀取圖片數(shù)據(jù)流程

讀取圖片數(shù)據(jù),需要關(guān)鍵的key。請求時,需要傳入封裝好的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讀取數(shù)據(jù)流程圖.png

六、BlobCache、DiskLruCache讀取時間對比

BlobCache與DiskLruCache的存儲和讀取速度對比

七、BlobCache在開發(fā)中的使用

這部分也放在下下一篇文章中

參考文章:

1、https://blog.csdn.net/Zj090308/article/details/51346471

2、https://www.pianshen.com/article/10661346238/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容