剖析LruCache

??提到Android的緩存策略任何一個Android開發(fā)人員都能隨口說出LruCache莉钙,利用最新最少使用(Least Recently Used)的原則進行緩存,可LRU到底是怎么實現(xiàn)的呢?

LruCache簡介

??LruCache是個泛型類宁赤,主要算法原理是把最近使用的對象用強引用(即我們平常使用的對象引用方式)存儲在 LinkedHashMap 中。當(dāng)緩存滿時贿讹,把最近最少使用的對象從內(nèi)存中移除苛聘,并提供了getput方法來完成緩存的獲取和添加操作。

實現(xiàn)原理

LruCache內(nèi)部維護的是一個LinkedHashMap秃流,最近最少的算法判斷邏輯都是由LinkedHashMap來實現(xiàn)赂蕴,如果對LinkedHashMap原理不了解的童鞋,可以先了解下LinkedHashMap的原理分析這篇文章剔应。

初始化

private final LinkedHashMap<K, V> map;

    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;    //當(dāng)前cache的大小
    private int maxSize; //cache最大大小

    private int putCount;       //put的次數(shù)
    private int createCount;    //create的次數(shù)
    private int evictionCount;  //回收的次數(shù)
    private int hitCount;       //命中的次數(shù)
    private int missCount;      //未命中次數(shù)

    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        //將LinkedHashMap的accessOrder設(shè)置為true來實現(xiàn)LRU
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);  
    }

put

public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);  //size加上預(yù)put對象的大小
            previous = map.put(key, value);
            if (previous != null) {
                //如果之前存在鍵為key的對象睡腿,則size應(yīng)該減去原來對象的大小
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
        //每次新加入對象都需要調(diào)用trimToSize方法看是否需要回收
        trimToSize(maxSize);
        return previous;
    }

由上面的代碼可以看出來,首先把size增加峻贮,然后判斷是否以前已經(jīng)有元素席怪,如果有,就更新當(dāng)前的size,并且調(diào)用entryRemoved方法,entryRemoved是一個空實現(xiàn)纤控,如果我們使用LruCache的時候需要掌握元素移除的信息挂捻,可以重寫這個方法。最后就會調(diào)用trimToSize船万,來調(diào)整集合中的內(nèi)容刻撒。

trimToSize 方法

/**
     * @param maxSize the maximum size of the cache before returning. May be -1
     *     to evict even 0-sized elements.
     * 此方法根據(jù)maxSize來調(diào)整內(nèi)存cache的大小,如果maxSize傳入-1耿导,則清空緩存中的所有對象
     */
    private void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
                //如果當(dāng)前size小于maxSize或者map沒有任何對象,則結(jié)束循環(huán)
                if (size <= maxSize || map.isEmpty()) {
                    break;
                }
                //移除鏈表頭部的元素声怔,并進入下一次循環(huán)
                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;  //回收次數(shù)+1
            }

            entryRemoved(true, key, value, null);
        }
    }

這個方法是一個無限循環(huán),跳出循環(huán)的條件是舱呻,size < maxSize或者 map 為醋火。主要的功能是判斷當(dāng)前容量時候已經(jīng)超出最大的容量,如果超出了maxSize的話箱吕,就會循環(huán)移除map中的第一個元素芥驳,直到達到跳出循環(huán)的條件。由上面的分析知道茬高,map中的第一個元素就是最近最少使用的那個元素兆旬。

get

/**
     * Returns the value for {@code key} if it exists in the cache or can be
     * created by {@code #create}. If a value was returned, it is moved to the
     * head of the queue. This returns null if a value is not cached and cannot
     * be created.
     * 通過key獲取相應(yīng)的item,或者創(chuàng)建返回相應(yīng)的item怎栽。相應(yīng)的item會移動到隊列的尾部丽猬,
     * 如果item的value沒有被cache或者不能被創(chuàng)建宿饱,則返回null。
     */
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                //mapValue不為空表示命中宝鼓,hitCount+1并返回mapValue對象
                hitCount++;
                return mapValue;
            }
            missCount++;  //未命中
        }

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         * 如果未命中刑棵,則試圖創(chuàng)建一個對象,這里create方法返回null,并沒有實現(xiàn)創(chuàng)建對象的方法
         * 如果需要事項創(chuàng)建對象的方法可以重寫create方法愚铡。因為圖片緩存時內(nèi)存緩存沒有命中會去
         * 文件緩存中去取或者從網(wǎng)絡(luò)下載蛉签,所以并不需要創(chuàng)建。
         */
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }
        //假如創(chuàng)建了新的對象沥寥,則繼續(xù)往下執(zhí)行
        synchronized (this) {
            createCount++;  
            //將createdValue加入到map中碍舍,并且將原來鍵為key的對象保存到mapValue
            mapValue = map.put(key, createdValue);   
            if (mapValue != null) {
                // There was a conflict so undo that last put
                //如果mapValue不為空,則撤銷上一步的put操作邑雅。
                map.put(key, mapValue);
            } else {
                //加入新創(chuàng)建的對象之后需要重新計算size大小
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            //每次新加入對象都需要調(diào)用trimToSize方法看是否需要回收
            trimToSize(maxSize);
            return createdValue;
        }
    }

這個方法就先通過key來獲取value,如果能獲取到就就直接返回片橡,獲取不到的話,就調(diào)用create()方法創(chuàng)建一個淮野,事實上捧书,如果我們不重寫這個create方法的話是return null的,所以整個流程就是獲取得到就直接返回骤星,獲取不到就返回null经瓷。

不要以為LruCache是多么復(fù)雜的一個算法,看過之后是不是感覺也就那么回事洞难?-

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舆吮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子队贱,更是在濱河造成了極大的恐慌色冀,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柱嫌,死亡現(xiàn)場離奇詭異锋恬,居然都是意外死亡,警方通過查閱死者的電腦和手機编丘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門与学,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瘪吏,你說我怎么就攤上這事∥锨桑” “怎么了掌眠?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幕屹。 經(jīng)常有香客問我蓝丙,道長级遭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任渺尘,我火速辦了婚禮挫鸽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鸥跟。我一直安慰自己丢郊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布医咨。 她就那樣靜靜地躺著枫匾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拟淮。 梳的紋絲不亂的頭發(fā)上干茉,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音很泊,去河邊找鬼角虫。 笑死,一個胖子當(dāng)著我的面吹牛委造,可吹牛的內(nèi)容都是我干的戳鹅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼争涌,長吁一口氣:“原來是場噩夢啊……” “哼粉楚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起亮垫,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤模软,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饮潦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體燃异,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年继蜡,在試婚紗的時候發(fā)現(xiàn)自己被綠了回俐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡稀并,死狀恐怖仅颇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碘举,我是刑警寧澤忘瓦,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站引颈,受9級特大地震影響耕皮,放射性物質(zhì)發(fā)生泄漏境蜕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一凌停、第九天 我趴在偏房一處隱蔽的房頂上張望粱年。 院中可真熱鬧,春花似錦罚拟、人聲如沸台诗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拉庶。三九已至,卻和暖如春秃励,著一層夾襖步出監(jiān)牢的瞬間氏仗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工夺鲜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留皆尔,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓币励,卻偏偏與公主長得像慷蠕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子食呻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容