理解LruCache

理解LruCache

LRU(Least Recently Used)緩存算法霞势,即為最近最少使用算法,它的核心思想是當(dāng)緩存滿時,會優(yōu)先淘汰那些近期最少使用的緩存對象。

LruCache是一個泛型類曹锨,它內(nèi)部采用了一個LinkedHashMap以強(qiáng)引用的方式存儲外界的緩存對象,其提供get和put方法來完成緩存的獲取和添加剃允。

下面我們具體看下LruCache.java這個類:

public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;
    
    private int size;           // 當(dāng)前大小
    private int maxSize;        // 最大容量

    private int putCount;       // put次數(shù)
    private int createCount;    // 創(chuàng)建次數(shù)
    private int evictionCount;  // 回收次數(shù)
    private int hitCount;       // 命中次數(shù)
    private int missCount;      // 未命中次數(shù)
    ......
}

可以看到LruCache主要維護(hù)一個LinkedHashMap沛简,主要算法原理是把最近使用的對象引用存儲在 LinkedHashMap 中。
接下來看構(gòu)造函數(shù):

public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

主要設(shè)置Cache最大值maxSize斥废,初始化一個LinkedHashMap椒楣, accessOrder 設(shè)置為 true 來實現(xiàn) LRU,利用訪問順序而不是插入順序牡肉。

public void resize(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }

    synchronized (this) {
        this.maxSize = maxSize;
    }
    trimToSize(maxSize);
}

重新設(shè)定Cache最大值

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) {
            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.
     */

    V createdValue = create(key);
    if (createdValue == null) {
        return null;
    }

    synchronized (this) {
        createCount++;
        mapValue = map.put(key, createdValue);

        if (mapValue != null) {
            // There was a conflict so undo that last put
            map.put(key, mapValue);
        } else {
            size += safeSizeOf(key, createdValue);
        }
    }

    if (mapValue != null) {
        entryRemoved(false, key, createdValue, mapValue);
        return mapValue;
    } else {
        trimToSize(maxSize);
        return createdValue;
    }
}

get方法捧灰,如果Cache中存在該item,返回item统锤;如果不存在毛俏,則創(chuàng)建item。

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);
        previous = map.put(key, value);
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }

    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }

    trimToSize(maxSize);
    return previous;
}

往Cache中put相應(yīng)item饲窿。

對于get和put的基本原理煌寇,這里引用一篇文章中的圖片說明圖片來源

LruCache基本原理

這張圖很好地說明了最近最少的思想。

上面幾個方法中都提到了trimToSize:

public 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!");
            }

            if (size <= maxSize) {
                break;
            }

            Map.Entry<K, V> toEvict = map.eldest();
            if (toEvict == null) {
                break;
            }

            key = toEvict.getKey();
            value = toEvict.getValue();
            map.remove(key);
            size -= safeSizeOf(key, value);
            evictionCount++;
        }

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

重新計算Cache大小免绿,將最久沒有用到的eldest刪除唧席。

public final V remove(K key) {
    if (key == null) {
        throw new NullPointerException("key == null");
    }

    V previous;
    synchronized (this) {
        previous = map.remove(key);
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }

    if (previous != null) {
        entryRemoved(false, key, previous, null);
    }

    return previous;
}

刪除key

protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

當(dāng) item 被回收或者刪掉時調(diào)用。該方法當(dāng) value 被回收釋放存儲空間時被 remove 調(diào)用,或者替換 item 值時 put 調(diào)用淌哟,默認(rèn)實現(xiàn)什么都沒做迹卢,使用時可以根據(jù)需要重寫。

protected V create(K key) {
    return null;
}

同樣徒仓,可以根據(jù)需要重寫腐碱。

private int safeSizeOf(K key, V value) {
    int result = sizeOf(key, value);
    if (result < 0) {
        throw new IllegalStateException("Negative size: " + key + "=" + value);
    }
    return result;
}

protected int sizeOf(K key, V value) {
    return 1;
}

返回一組key/value的entry大小,默認(rèn)為1

public final void evictAll() {
    trimToSize(-1); // -1 will evict 0-sized elements
    }

清空Cache掉弛,trimToSize(-1)症见。

/**
 * For caches that do not override {@link #sizeOf}, this returns the number
 * of entries in the cache. For all other caches, this returns the sum of
 * the sizes of the entries in this cache.
 */
public synchronized final int size() {
    return size;
}

/**
 * For caches that do not override {@link #sizeOf}, this returns the maximum
 * number of entries in the cache. For all other caches, this returns the
 * maximum sum of the sizes of the entries in this cache.
 */
public synchronized final int maxSize() {
    return maxSize;
}

/**
 * Returns the number of times {@link #get} returned a value that was
 * already present in the cache.
 */
public synchronized final int hitCount() {
    return hitCount;
}

/**
 * Returns the number of times {@link #get} returned null or required a new
 * value to be created.
 */
public synchronized final int missCount() {
    return missCount;
}

/**
 * Returns the number of times {@link #create(Object)} returned a value.
 */
public synchronized final int createCount() {
    return createCount;
}

/**
 * Returns the number of times {@link #put} was called.
 */
public synchronized final int putCount() {
    return putCount;
}

/**
 * Returns the number of values that have been evicted.
 */
public synchronized final int evictionCount() {
    return evictionCount;
}

/**
 * Returns a copy of the current contents of the cache, ordered from least
 * recently accessed to most recently accessed.
 */
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);
}

其他一些方法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末殃饿,一起剝皮案震驚了整個濱河市谋作,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乎芳,老刑警劉巖遵蚜,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奈惑,居然都是意外死亡吭净,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門肴甸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寂殉,“玉大人,你說我怎么就攤上這事原在∮讶牛” “怎么了?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵晤斩,是天一觀的道長焕檬。 經(jīng)常有香客問我姆坚,道長澳泵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任兼呵,我火速辦了婚禮兔辅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘击喂。我一直安慰自己维苔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布懂昂。 她就那樣靜靜地躺著介时,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沸柔,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天循衰,我揣著相機(jī)與錄音,去河邊找鬼褐澎。 笑死会钝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的工三。 我是一名探鬼主播迁酸,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼俭正!你這毒婦竟也來了奸鬓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤掸读,失蹤者是張志新(化名)和其女友劉穎全蝶,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寺枉,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抑淫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了姥闪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片始苇。...
    茶點(diǎn)故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖筐喳,靈堂內(nèi)的尸體忽然破棺而出催式,到底是詐尸還是另有隱情,我是刑警寧澤避归,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布荣月,位于F島的核電站,受9級特大地震影響梳毙,放射性物質(zhì)發(fā)生泄漏哺窄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一账锹、第九天 我趴在偏房一處隱蔽的房頂上張望萌业。 院中可真熱鬧,春花似錦奸柬、人聲如沸生年。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抱婉。三九已至档叔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒸绩,已是汗流浹背蹲蒲。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侵贵,地道東北人届搁。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像窍育,于是被迫代替她去往敵國和親卡睦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評論 2 356

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