LruCache源碼分析

LruCache的源碼分析已經(jīng)很多了,看了很多遍,但是自己走一遍分析,才是真正的掌握,將知識(shí)轉(zhuǎn)化到自身。

用途

LruCache的用途就是緩存,但是緩存并不能一直無限量的緩存,設(shè)備內(nèi)存始終是有限的,所以緩存需要有一個(gè)刪除策略。

常見例子

圖片加載基本是每個(gè)App的必備項(xiàng)了,雖然我們使用都是第三方加載框架,但是加載框架內(nèi)部也會(huì)緩存圖片Bitmap對(duì)象,也是使用LruCache來緩存,所以我們了解LruCache,對(duì)加載框架的緩存邏輯也能知道核心了。

  • 啟動(dòng)一個(gè)AsyncTask任務(wù),OkHttp加載圖片Bitmap,從網(wǎng)絡(luò)獲取前先從LruCache中查詢是否有緩存,有則直接返回,無則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求完畢再保存到LruCache緩存中,下次再去獲取則會(huì)命中緩存,復(fù)用緩存而無需請(qǐng)求。
  1. 創(chuàng)建圖片緩存,緩存大小為設(shè)備最大內(nèi)存容量的8分之一。創(chuàng)建LruCache,我們一般需要復(fù)寫sizeOf(),LruCache會(huì)回調(diào)sizeOf()來獲取緩存對(duì)象占用的內(nèi)存大小。

還有2個(gè)可選復(fù)寫的方法:
- entryRemoved()為緩存對(duì)象被刪除時(shí)的回調(diào)。
- create()為獲取不到緩存時(shí)調(diào)用,我們可以新建緩存對(duì)象,但在圖片緩存中我們并不需要,返回null或者不復(fù)寫即可(默認(rèn)就是返回null),請(qǐng)求完網(wǎng)絡(luò)后再保存,或者在這里請(qǐng)求再返回,但是不建議,畢竟每個(gè)緩存對(duì)象的獲取方式不同。

/**
 * 創(chuàng)建圖片緩存
 */
private void createImageCache() {
    //取設(shè)備內(nèi)存最大容量的8分之一作為緩存大小
    long maxMemorySize = Runtime.getRuntime().maxMemory();
    int cacheSize = (int) (maxMemorySize / 8);
    mLruCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap value) {
            //計(jì)算Bitmap占用的內(nèi)存
            return getBitmapSize(value);
        }

        @Override
        protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
            super.entryRemoved(evicted, key, oldValue, newValue);
            Log.d(TAG, "緩存對(duì)象被刪除");
        }

        @Override
        protected Bitmap create(String key) {
            //get()方法獲取不到緩存時(shí)調(diào)用,我們不返回?cái)?shù)據(jù),讓去請(qǐng)求網(wǎng)絡(luò)獲取
            return super.create(key);
        }
    };
}
  1. 新建AsyncTask異步任務(wù),doInBackground()中,先查詢緩存是否存在,不存在再讓請(qǐng)求網(wǎng)絡(luò),再保存到網(wǎng)絡(luò),如果緩存存在,則直接復(fù)用緩存。
/**
 * 兼容獲取Bitmap大小
 */
private int getBitmapSize(Bitmap bitmap) {
    //API 19
    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
        return bitmap.getAllocationByteCount();
    }
    //API 12
    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.HONEYCOMB_MR1) {
        return bitmap.getByteCount();
    }
    // Earlier Version
    return bitmap.getRowBytes() * bitmap.getHeight();
}

private static class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    private OkHttpClient mClient;
    private Callback mCallback;

    /**
     * 回調(diào)接口
     */
    public interface Callback {
        /**
         * 執(zhí)行前回調(diào)
         */
        void onStart();

        /**
         * 執(zhí)行后回調(diào)
         *
         * @param bitmap 執(zhí)行結(jié)果
         */
        void onFinish(Bitmap bitmap);

        /**
         * 嘗試獲取緩存
         */
        Bitmap getCache(String key);
    }

    public DownloadImageTask(OkHttpClient client, Callback callback) {
        mClient = client;
        mCallback = callback;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mCallback.onStart();
    }

    @Override
    protected Bitmap doInBackground(String... urls) {
        try {
            String url = urls[0];
            //1.先從內(nèi)存緩存中找
            Bitmap cacheBitmap = mCallback.getCache(url);
            if (cacheBitmap != null) {
                Log.d(TAG, "緩存命中");
                return cacheBitmap;
            }
            Log.d(TAG, "緩存不命中,請(qǐng)求網(wǎng)絡(luò)");
            //2.緩存找不到,請(qǐng)求網(wǎng)絡(luò)
            Request request = new Request.Builder()
                    .url(url)
                    .build();
            Call call = mClient.newCall(request);
            Response response = call.execute();
            return BitmapFactory.decodeStream(response.body().byteStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        mCallback.onFinish(bitmap);
    }
}

原理

LruCache的緩存是使用的最近最少使用的策略,當(dāng)訪問元素時(shí),將元素移動(dòng)到表尾,當(dāng)緩存容量到達(dá)最大值時(shí)移除表頭元素(最少使用的元素)。

使用Key-Value的方式緩存數(shù)據(jù)自然想到Map這種鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu),而LruCache的策略則使用了LinkedHashMap。

為什么使用LinkedHashMap呢?因?yàn)長(zhǎng)inkedHashMap的構(gòu)造方法,有一個(gè)accessOrder參數(shù),默認(rèn)為false,則為按插入順序排序,true則使用訪問順序排序,訪問越多,越排得后。使用LinkedHashMap則天然支持最近最少使用的策略。

構(gòu)造方法。

  • 指定最大容量為參數(shù)創(chuàng)建。不允許配置小于等于0.
  • 并創(chuàng)建了一個(gè)LinkedHashMap,指定最后的accessOrder字段為true,則代表按訪問順序排序,將經(jīng)常訪問的元素放到表尾。
/**
 * @param maxSize 最大緩存容量
 */
public LruCache(int maxSize) {
    //最大緩存的對(duì)象大小,不能小于等于0,否則拋出異常
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    //緩存映射,最后的accessOrder字段設(shè)置為true,則按照訪問的順序排序,否則以插入的順序排序
    this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

添加緩存元素。

  • key、value都不能為null,否則拋出異常。
  • 同步鎖,處理并發(fā),safeSizeOf()就是調(diào)用到我們的sizeOf()獲取緩存對(duì)象的占用大小。并將占用大小和已占用大小累加。
  • 通過map.put()添加緩存元素。put()方法有一個(gè)返回值,意思是如果之前已經(jīng)有key保存了,則將之前保存的value返回,再覆蓋上新的value。
  • 如果put()有返回值,則將他的占用大小和累加值相減。
  • 由于刪除了舊值,所以回調(diào)entryRemoved()。
  • put()操作改變了緩存,調(diào)用trimToSize()調(diào)整內(nèi)存。
/**
 * 添加緩存
 *
 * @param key   緩存Key
 * @param value 緩存值
 */
public final V put(K key, V value) {
    //不能存儲(chǔ)null鍵和null值
    if (key == null || value == null) {
        throw new NullPointerException("key == null || value == null");
    }
    //以前的值,只有當(dāng)map里面已經(jīng)記錄了key,通過put方法就會(huì)返回之前緩存的value
    V previous;
    synchronized (this) {
        //插入的數(shù)量自增
        putCount++;
        //將已緩存的對(duì)象大小和本次添加的緩存對(duì)象的大小相加
        size += safeSizeOf(key, value);
        //添加緩存
        previous = map.put(key, value);
        //之前添加過,將大小減回去
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }
    //由于本次緩存會(huì)覆蓋之前的值,所以相當(dāng)于刪除之前的值,回調(diào)entryRemoved通知元素被刪除
    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }
    //每次調(diào)整緩存,都去判斷如果緩存滿了,按照LRU最近最少使用策略刪除緩存
    trimToSize(maxSize);
    return previous;
}

修整緩存占用

  • 開啟一個(gè)死循環(huán),不斷檢查當(dāng)前占用內(nèi)存是否大于最大值,否則一直循環(huán)刪除最近最少使用的對(duì)象,直到內(nèi)存占用小于最大值才停止。
  • map遍歷,找出表頭元素。
  • 通過map.remove()移除最近最少使用的元素。
  • 由于刪除了元素,調(diào)用safeSizeOf()調(diào)整內(nèi)存計(jì)數(shù)。
  • 回調(diào)entryRemoved()提示刪除了元素。
/**
 * 整理緩存,如果內(nèi)存超出,刪除表頭元素
 */
private void trimToSize(int maxSize) {
    //開啟死循環(huán)
    while (true) {
        K key;
        V value;
        synchronized (this) {
            //參數(shù)檢查
            if (size < 0 || (map.isEmpty() && size != 0)) {
                throw new IllegalStateException(getClass().getName()
                        + ".sizeOf() is reporting inconsistent results!");
            }
            //一直死循環(huán),直到緩存的對(duì)象內(nèi)存小于最大值
            if (size <= maxSize) {
                break;
            }
            //不斷獲取第一個(gè)元素,不斷while循環(huán)刪除,直到內(nèi)存大小比最大內(nèi)存大小小才停下
            Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
            //如果為null不處理
            if (toEvict == null) {
                break;
            }
            //獲取本次要?jiǎng)h除的元素鍵值
            key = toEvict.getKey();
            value = toEvict.getValue();
            //移除元素
            map.remove(key);
            //將刪除的元素的內(nèi)存減去
            size -= safeSizeOf(key, value);
            //回收次數(shù)自增
            evictionCount++;
        }
        //調(diào)用entryRemoved()提醒刪除了元素
        entryRemoved(true, key, value, null);
    }
}

獲取緩存元素

  • 不允許key為null,否則拋出異常。
  • 同步鎖包保證并發(fā)獲取。
  • 通過map.get(key)獲取緩存元素。
  • 獲取不到緩存元素,則調(diào)用create(key)來新建元素。
  • create()獲取新建的元素不為null,則將元素保存到map,處理就和上面的put()處理流程是一致的。
  • 操作完畢,調(diào)用trimToSize(),檢查是否需要清理內(nèi)存。
/**
 * 獲取緩存,會(huì)將本次獲取的元素放到表尾
 */
public final V get(K key) {
    //不允許key為null
    if (key == null) {
        throw new NullPointerException("key == null");
    }

    V mapValue;
    synchronized (this) {
        //從map中獲取value,由于我們?cè)O(shè)置了accessOrder為true,所以會(huì)將元素移動(dòng)到表尾
        mapValue = map.get(key);
        if (mapValue != null) {
            hitCount++;
            return mapValue;
        }
        missCount++;
    }

    //map中獲取不到元素,調(diào)用create()方法創(chuàng)建一個(gè)
    V createdValue = create(key);
    if (createdValue == null) {
        return null;
    }
    //將對(duì)象緩存到map和put()方法是一樣的
    synchronized (this) {
        createCount++;
        //put添加緩存,但是如果返回了舊值,則將舊值保存回去(意外)
        mapValue = map.put(key, createdValue);
        if (mapValue != null) {
            //保存原來的值
            map.put(key, mapValue);
        } else {
            size += safeSizeOf(key, createdValue);
        }
    }
    //有舊值,從map中刪除掉
    if (mapValue != null) {
        entryRemoved(false, key, createdValue, mapValue);
        return mapValue;
    } else {
        //每次調(diào)整緩存,都去判斷如果緩存滿了,按照LRU最近最少使用策略刪除緩存
        trimToSize(maxSize);
        return createdValue;
    }
}

移除緩存元素

  • 同樣key不能為null,否則拋出異常。
  • 通過map.remove(key)移除元素
  • 調(diào)用safeSizeOf()計(jì)算內(nèi)存占用大小。
  • 調(diào)用entryRemoved()通知緩存元素被刪除。
/**
 * 移除元素
 *
 * @return 元素Key
 */
public final V remove(K key) {
    //不允許key為null
    if (key == null) {
        throw new NullPointerException("key == null");
    }
    //移除的元素
    V previous;
    synchronized (this) {
        //從map中移除元素
        previous = map.remove(key);
        //改變緩存的大小
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }
    //回調(diào)entryRemoved告知元素被刪除
    if (previous != null) {
        entryRemoved(false, key, previous, null);
    }
    return previous;
}

完整源碼和注釋

public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;

    /**
     * 當(dāng)前緩存的對(duì)象的總內(nèi)存
     */
    private int size;
    /**
     * 配置的最大緩存容量
     */
    private int maxSize;

    /**
     * 調(diào)用put()方法緩存對(duì)象的次數(shù)
     */
    private int putCount;
    /**
     * 調(diào)用create()方法創(chuàng)建對(duì)象的次數(shù)
     */
    private int createCount;
    /**
     * 調(diào)用trimToSize()方法回收對(duì)象的次數(shù)
     */
    private int evictionCount;
    /**
     * 取調(diào)用get()命中緩存的次數(shù)
     */
    private int hitCount;
    /**
     * 調(diào)用get()方法不命中的次數(shù)
     */
    private int missCount;

    /**
     * @param maxSize 最大緩存容量
     */
    public LruCache(int maxSize) {
        //最大緩存的對(duì)象大小,不能小于等于0,否則拋出異常
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        //緩存映射,最后的accessOrder字段設(shè)置為true,則按照訪問的順序排序,否則以插入的順序排序
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

    /**
     * 重新設(shè)置最大緩存大小
     *
     * @param maxSize 新的緩存大小
     */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        //同步鎖同步設(shè)置最大值
        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

    /**
     * 獲取緩存,會(huì)將本次獲取的元素放到表尾
     */
    public final V get(K key) {
        //不允許key為null
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            //從map中獲取value,由于我們?cè)O(shè)置了accessOrder為true,所以會(huì)將元素移動(dòng)到表尾
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }

        //map中獲取不到元素,調(diào)用create()方法創(chuàng)建一個(gè)
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }
        //將對(duì)象緩存到map和put()方法是一樣的
        synchronized (this) {
            createCount++;
            //put添加緩存,但是如果返回了舊值,則將舊值保存回去(意外)
            mapValue = map.put(key, createdValue);
            if (mapValue != null) {
                //保存原來的值
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }
        //有舊值,從map中刪除掉
        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            //每次調(diào)整緩存,都去判斷如果緩存滿了,按照LRU最近最少使用策略刪除緩存
            trimToSize(maxSize);
            return createdValue;
        }
    }

    /**
     * 添加緩存
     *
     * @param key   緩存Key
     * @param value 緩存值
     */
    public final V put(K key, V value) {
        //不能存儲(chǔ)null鍵和null值
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }
        //以前的值,只有當(dāng)map里面已經(jīng)記錄了key,通過put方法就會(huì)返回之前緩存的value
        V previous;
        synchronized (this) {
            //插入的數(shù)量自增
            putCount++;
            //將已緩存的對(duì)象大小和本次添加的緩存對(duì)象的大小相加
            size += safeSizeOf(key, value);
            //添加緩存
            previous = map.put(key, value);
            //之前添加過,將大小減回去
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
        //由于本次緩存會(huì)覆蓋之前的值,所以相當(dāng)于刪除之前的值,回調(diào)entryRemoved通知元素被刪除
        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
        //每次調(diào)整緩存,都去判斷如果緩存滿了,按照LRU最近最少使用策略刪除緩存
        trimToSize(maxSize);
        return previous;
    }

    /**
     * 整理緩存,如果內(nèi)存超出,刪除表頭元素
     */
    private void trimToSize(int maxSize) {
        //開啟死循環(huán)
        while (true) {
            K key;
            V value;
            synchronized (this) {
                //參數(shù)檢查
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
                //一直死循環(huán),直到緩存的對(duì)象內(nèi)存小于最大值
                if (size <= maxSize) {
                    break;
                }
                //不斷獲取第一個(gè)元素,不斷while循環(huán)刪除,直到內(nèi)存大小比最大內(nèi)存大小小才停下
                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                //如果為null不處理
                if (toEvict == null) {
                    break;
                }
                //獲取本次要?jiǎng)h除的元素鍵值
                key = toEvict.getKey();
                value = toEvict.getValue();
                //移除元素
                map.remove(key);
                //將刪除的元素的內(nèi)存減去
                size -= safeSizeOf(key, value);
                //回收次數(shù)自增
                evictionCount++;
            }
            //調(diào)用entryRemoved()提醒刪除了元素
            entryRemoved(true, key, value, null);
        }
    }

    /**
     * 移除元素
     *
     * @return 元素Key
     */
    public final V remove(K key) {
        //不允許key為null
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        //移除的元素
        V previous;
        synchronized (this) {
            //從map中移除元素
            previous = map.remove(key);
            //改變緩存的大小
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
        //回調(diào)entryRemoved告知元素被刪除
        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }
        return previous;
    }

    /**
     * 告知元素被刪除,我們可以復(fù)寫這個(gè)方法
     */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
    }

    /**
     * 獲取不到元素,告知我們創(chuàng)建一個(gè)
     */
    protected V create(K key) {
        return null;
    }

    /**
     * 實(shí)際就是調(diào)用了sizeOf()方法
     */
    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }

    /**
     * 返回對(duì)象的內(nèi)存占用大小,默認(rèn)為,一般我們都會(huì)重寫
     */
    protected int sizeOf(K key, V value) {
        return 1;
    }

    /**
     * 清除所有緩存
     */
    public final void evictAll() {
        //-1代表清除所有元素
        trimToSize(-1);
    }

    /**
     * 獲取已緩存的對(duì)象總大小
     */
    public synchronized final int size() {
        return size;
    }

    /**
     * 獲取配置的對(duì)象緩存最大大小
     */
    public synchronized final int maxSize() {
        return maxSize;
    }

    /**
     * 獲取調(diào)用get()命中緩存的次數(shù)
     */
    public synchronized final int hitCount() {
        return hitCount;
    }

    /**
     * 獲取調(diào)用get()方法不命中的次數(shù)
     */
    public synchronized final int missCount() {
        return missCount;
    }

    /**
     * 獲取調(diào)用create()方法創(chuàng)建對(duì)象的次數(shù)
     */
    public synchronized final int createCount() {
        return createCount;
    }

    /**
     * 獲取調(diào)用put()方法緩存對(duì)象的次數(shù)
     */
    public synchronized final int putCount() {
        return putCount;
    }

    /**
     * 獲取調(diào)用trimToSize()方法回收對(duì)象的次數(shù)
     */
    public synchronized final int evictionCount() {
        return evictionCount;
    }

    /**
     * 獲取緩存map的一個(gè)副本
     */
    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }

    @Override
    public synchronized final String toString() {
        int accesses = hitCount + missCount;
        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
                maxSize, hitCount, missCount, hitPercent);
    }
}

總結(jié)

LruCache的最近最少使用的策略是通過LinkedHashMap,將LinkedHashMap的構(gòu)造方法的accessOrder字段設(shè)置true,讓LinkedHashMap按訪問順序排序,不斷將常用的元素放到表尾,不常用的元素放到表頭。每次增、刪、改都判斷緩存內(nèi)存是否大于最大緩存值,如果大于則用一個(gè)死循環(huán)不斷將表頭元素移除,直到內(nèi)存小于最大內(nèi)存。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。