Glide都在用LruCache,你會(huì)多少呢寞冯?

前言

說到Glide就有點(diǎn)尷尬渴析,我本來想出一篇《手撕Glide》,但是很遺憾吮龄,源碼實(shí)在太多了俭茧。寫著寫著就3000多字了,甚至還沒寫完漓帚,實(shí)在不合適母债,因?yàn)槲覍懳牡脑瓌t是短小精悍,所以就暫時(shí)不出這篇文章了尝抖,這次就先講講Glide都在用的LruCache有什么神奇之處场斑。而且我抖音的面試在即,也不知道自己水平到了沒有牵署,現(xiàn)在出一篇算一篇先。

思維導(dǎo)圖

使用方法及結(jié)果

在項(xiàng)目中直接導(dǎo)入Glide的庫喧半,調(diào)用內(nèi)部的LruCache來看看效果奴迅。

LruCache lruCache = new LruCache<String, Integer>(2);
lruCache.put("1", 1);
lruCache.put("2", 2);
lruCache.put("1", 1);
lruCache.put("3", 3);
System.out.println(lruCache.get("1"));
System.out.println(lruCache.get("2"));
System.out.println(lruCache.get("3"));

簡要說明代碼內(nèi)容,創(chuàng)建一個(gè)空間為2的存儲(chǔ)空間(這里先不透漏內(nèi)部結(jié)構(gòu))挺据,用put()方法對(duì)數(shù)據(jù)進(jìn)行存儲(chǔ)取具,再通過get()對(duì)每個(gè)數(shù)據(jù)進(jìn)行一次獲取操作,然后我們?cè)賮砜纯唇Y(jié)果扁耐。

我的天O炯臁!2沒了婉称? 這是怎么一回事块仆??為了知道答案王暗,那我們只好進(jìn)入Glide的庫中看看原因了悔据。

LruCache源碼導(dǎo)讀

先看看LruCache的變量家庭里有哪些小家伙把。

public class LruCache<T, Y> {
  // 容量為100的雙向鏈表
  private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true); 
  private final long initialMaxSize; // 初始化最大容量
  private long maxSize; // 最大容量
  private long currentSize; // 已存在容量
}

同樣對(duì)于LruCache來說不也和HashMap一樣只有三步驟要走嘛俗壹,那我就從這三個(gè)步驟入手探索一下LruCache好了科汗,但是我們要帶上一個(gè)問題出發(fā)initialMaxSize的作用是什么绷雏?

new LruCache<T, Y>(size)

  public LruCache(long size) {
    this.initialMaxSize = size;
    this.maxSize = size;
  }

到這里想來讀者都已經(jīng)知道套路了头滔,也就初始化了初始化最大容量和最大容量怖亭,那就直接下一步。

put(key, value)

public synchronized Y put(@NonNull T key, @Nullable Y item) {
    // 返回值就是一個(gè)1
    final int itemSize = getSize(item);
    // 如果1大于等于最大值就無操作
    // 也就說明整個(gè)初始化的時(shí)候并不能將size設(shè)置成1
    if (itemSize >= maxSize) {
      //用于重寫的保留方法
      onItemEvicted(key, item);
      return null;
    }
    // 對(duì)當(dāng)前存在數(shù)據(jù)容量加一
    if (item != null) {
      currentSize += itemSize;
    }
    @Nullable final Y old = cache.put(key, item);
    if (old != null) {
      currentSize -= getSize(old);
    
      if (!old.equals(item)) {
        onItemEvicted(key, old);
      }
    }
    evict(); // 1 -->

    return old;
  }
// 由注釋1直接調(diào)用的方法
private void evict() {
    trimToSize(maxSize); // 2 -->
  }
// 由注釋2直接調(diào)用的方法 
protected synchronized void trimToSize(long size) {
    Map.Entry<T, Y> last;
    Iterator<Map.Entry<T, Y>> cacheIterator;
    // 說明當(dāng)前的容量大于了最大容量
    // 需要對(duì)最后的數(shù)據(jù)進(jìn)行一個(gè)清理
    while (currentSize > size) {
      cacheIterator = cache.entrySet().iterator();
      last = cacheIterator.next();
      final Y toRemove = last.getValue();
      currentSize -= getSize(toRemove);
      final T key = last.getKey();
      cacheIterator.remove();
      onItemEvicted(key, toRemove);
    }
  }

這是一個(gè)帶鎖機(jī)制的方法坤检,通過對(duì)當(dāng)前容量和最大容量的判斷兴猩,來抉擇是否需要把我們的數(shù)據(jù)進(jìn)行一個(gè)刪除。但是問題依舊存在缀蹄,initialMaxSize的作用是什么峭跳?,我們能夠知道的是maxSize是一個(gè)用于控制容量大小的值缺前。

get()

 public synchronized Y get(@NonNull T key) {
    return cache.get(key);
  }

那這就是調(diào)用了LinkedHashMap中的數(shù)據(jù)蛀醉,但是終究還是沒有說出initialMaxSize的作用。

關(guān)于initialMaxSize

這里就不買關(guān)子了衅码,因?yàn)槠鋵?shí)就我的視角來看這個(gè)initialMaxSize確實(shí)是沒啥用處的拯刁。哈哈哈哈哈!J哦巍垛玻!但是,又一個(gè)地方用到了它奶躯。

public synchronized void setSizeMultiplier(float multiplier) {
    if (multiplier < 0) {
      throw new IllegalArgumentException("Multiplier must be >= 0");
    }
    maxSize = Math.round(initialMaxSize * multiplier);
    evict();
  }

也就是用于調(diào)控我們的最大容量大小帚桩,但是我覺得還是沒啥用,可是是我太菜了吧嘹黔,這個(gè)方法沒有其他調(diào)用它的方法账嚎,是一個(gè)我們直接在使用過程中使用的,可能和數(shù)據(jù)多次使用的一個(gè)保存之類的問題相關(guān)聯(lián)把儡蔓,場(chǎng)景的話也就類似Glide的圖片緩存加載把郭蕉。也希望知道的讀者能給我一個(gè)解答。

LinkedHashMap

因?yàn)椴僮鞣绞胶?code>HashMap一致就不再復(fù)述喂江,就看看他的節(jié)點(diǎn)長相召锈。

static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
        // 存在前后節(jié)點(diǎn),也就是我們所說的雙向鏈表
        LinkedHashMapEntry<K,V> before, after;
        LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }

但是到這里获询,我又出現(xiàn)了一個(gè)問題涨岁,為什么我沒有看到整個(gè)數(shù)據(jù)的移動(dòng)?也就是最近使用的數(shù)據(jù)應(yīng)該調(diào)換到最后開始的位置吉嚣,他到底實(shí)在哪里進(jìn)行處理的呢卵惦?做一個(gè)猜想好了,既然是使用了put()才會(huì)造成雙向鏈表中數(shù)據(jù)的變換瓦戚,那我們就應(yīng)該是需要進(jìn)入對(duì)LinkedHashMap.put()方法中進(jìn)行查詢沮尿。

當(dāng)然有興趣探索的讀者們,我需要提一個(gè)醒,就是這次的調(diào)用不可以直接進(jìn)行對(duì)put()進(jìn)行查詢畜疾,那樣只會(huì)調(diào)用到一個(gè)接口函數(shù)赴邻,或者是抽象類函數(shù),最適合的方法還是使用我們的斷點(diǎn)來進(jìn)行探索查詢啡捶。

但是經(jīng)過一段努力后姥敛,不斷深度調(diào)用探索發(fā)現(xiàn)這樣的問題,他最后會(huì)調(diào)用到這樣的問題瞎暑。

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { } // 把數(shù)據(jù)移動(dòng)到最后一位
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

這是之前我們?cè)诹私?code>HashMap是并沒有發(fā)現(xiàn)幾個(gè)方法彤敛,上面也明確寫著為LinkedHashMap保留。哇哦A硕摹墨榄!那我們的操作肯定實(shí)在這些里面了。

// --> HashMap源碼第656行附近調(diào)用到下方方法
// 在putVal()方法內(nèi)部存在這個(gè)出現(xiàn)
afterNodeAccess(e);
// --> LinkedHashMap對(duì)其具體實(shí)現(xiàn)
// 就是將當(dāng)前數(shù)據(jù)直接推到最后一個(gè)位置
// 也就是成為了最近剛使用過的數(shù)據(jù)
void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMapEntry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMapEntry<K,V> p =
                (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

好了勿她,自此我們也就清楚了整個(gè)鏈表的變換過程了害捕。

實(shí)戰(zhàn):手?jǐn)]LruCache

這是一個(gè)非常緊張刺激的環(huán)節(jié)了蝠引,擼代碼前双絮,我們來找找思路好了跺嗽。

(1)存儲(chǔ)容器用什么? 因?yàn)?code>LinkedHashMap的思路太過冗長砍聊,我們用數(shù)組來重新完成整個(gè)代碼的構(gòu)建

(2)關(guān)鍵調(diào)用方法put()背稼、get()以及put()涉及的已存在變量移位。

哇哦玻蝌!看來要做的事情也并沒有這么多,那我們就先來看看第一次構(gòu)造出來的框架好了雇庙。

public class LruCache {

    private Object objects[];
    private int maxSize;
    private int currentSize;

    public LruCache(int size){
        objects = new Object[size];
        maxSize = size;
    }

    /**
     * 插入item
     * @param item
     */
    public void put(Object item){
        
    }

    /**
     * 獲取item
     * @param item
     */
    public Object get(Object item){
        return null;
    }

    /**
     * 根據(jù)下標(biāo)對(duì)應(yīng),將后續(xù)數(shù)組移位
     * @param index
     */
    public void move(int index){
        
    }
}

因?yàn)橹灰菙?shù)組變換就存在移位灶伊,所以移位操作是必不可少的。那我們現(xiàn)在的工作也就是把數(shù)據(jù)填好了寒跳,對(duì)應(yīng)的移位是怎么樣的操作的思路了聘萨。

public class LruCache {

    public Object objects[];
    private int maxSize;
    public int currentSize;

    public LruCache(int size) {
        objects = new Object[size];
        maxSize = size;
    }

    /**
     * 插入item
     *
     * @param item
     */
    public void put(Object item) {
        // 容量未滿時(shí)分成兩種情況
        // 1。 容器內(nèi)存在
        // 2童太。 容器內(nèi)不存在
        int index = search(item);
        if (index == -1) {
            if (currentSize < maxSize) { //容器未滿米辐,直接插入
                objects[currentSize] = item;
                currentSize++;
            } else { // 容器已滿,刪去頭部插入
                move(0);
                objects[currentSize - 1] = item;
            }
        }else {
            move(index);
        }
    }

    /**
     * 獲取item
     *
     * @param item
     */
    public Object get(Object item) {
        int index = search(item);
        return index == -1 ? null : objects[index];
    }

    /**
     * 根據(jù)下標(biāo)對(duì)應(yīng)书释,將后續(xù)數(shù)組移位
     *
     * @param index
     */
    public void move(int index) {
        Object temp = objects[index];
        // 將后續(xù)數(shù)組移位
        for (int i = index; i < currentSize - 1; i++) {
            objects[i] = objects[i + 1];
        }
        objects[currentSize - 1] = temp;
    }

    /**
     * 搜尋數(shù)組中的數(shù)組
     * 存在則返回下標(biāo)
     * 不存在則返回 -1
     * @param item
     * @return
     */
    private int search(Object item) {
        for (int i = 0; i < currentSize; i++) {
            if (item.equals(objects[i])) return I;
        }
        return -1;
    }

因?yàn)橐呀?jīng)真的寫的比較詳細(xì)了翘贮,也沒什么難度的擼了我的20分鐘,希望讀者們能夠快入入門爆惧,下面給出我的一份測(cè)試樣例狸页,結(jié)束這個(gè)話題。

總結(jié)

想來我們都知道在操作系統(tǒng)中有這樣的問題需要思考,具體題型的話就是缺頁中斷芍耘。
用一個(gè)例題來徹底了解LruCache的算法址遇。

例: 存入內(nèi)存的數(shù)據(jù)序列為:(1,2斋竞,1倔约,3,2)坝初,內(nèi)存容量為2浸剩。

最近使用 最久未使用 動(dòng)作
1 1入內(nèi)存
2 1 2入內(nèi)存
1 2 1入內(nèi)存,交換1和2的使用頻率
3 1 3入內(nèi)存鳄袍,內(nèi)存不足绢要,排出2
2 3 2入內(nèi)存,內(nèi)存不足畦木,排出1

LruCache 主要用于緩存的處理,這里的緩存主要指的是內(nèi)存緩存和磁盤緩存袖扛。

以上就是我的學(xué)習(xí)成果,如果有什么我沒有思考到的地方或是文章內(nèi)存在錯(cuò)誤十籍,歡迎與我分享蛆封。


相關(guān)文章推薦:

面試中的HashMap、ConcurrentHashMap和Hashtable勾栗,你知道多少惨篱?

還不會(huì)七大排序,是準(zhǔn)備家里蹲嗎NХ砸讳?

手撕ButterKnife

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市界牡,隨后出現(xiàn)的幾起案子簿寂,更是在濱河造成了極大的恐慌,老刑警劉巖宿亡,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件常遂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡挽荠,警方通過查閱死者的電腦和手機(jī)克胳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來圈匆,“玉大人漠另,你說我怎么就攤上這事≡咀” “怎么了笆搓?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我砚作,道長窘奏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任葫录,我火速辦了婚禮着裹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘米同。我一直安慰自己骇扇,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布面粮。 她就那樣靜靜地躺著少孝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪熬苍。 梳的紋絲不亂的頭發(fā)上稍走,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音柴底,去河邊找鬼婿脸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛柄驻,可吹牛的內(nèi)容都是我干的狐树。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鸿脓,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼抑钟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起野哭,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤在塔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拨黔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛔溃,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蓉驹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揪利。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡态兴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疟位,到底是詐尸還是另有隱情瞻润,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站绍撞,受9級(jí)特大地震影響正勒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜傻铣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一章贞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧非洲,春花似錦鸭限、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至梦染,卻和暖如春赡麦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帕识。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國打工泛粹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渡冻。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓戚扳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親族吻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帽借,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355