LruCache源碼的理解

LruCache源碼的理解

使用場景

在Android手機上加載圖片,一般會用到三級緩存策略
這裏寫圖片描述

內存的緩存策略,一般會用到LruCache來解決

內存用於緩存遇到的問題

 1. 手機給每個應用分配的內存空間是有限的,如果內存中數據量過大,會造成OOM,所以,我們需要適當的清理內存
 2. 在清理內存的時候,我們希望最老的部分最先被清除,從而讓出空間來

LruCache中使用LinkedHashMap可以解決的問題

1. LinkedHashMap可以當做一個容器來使用
2. LinkedHashMap是一個Map,可以用key來給數據做標記
3. LinkedHashMap支持訪問順序,每次get()或者add()一個數據,都會把相應的數據放到鏈表的尾部,這樣,鏈表頭部的數據就是沒怎麼用過的數據,也就是最老的數據

圖形解釋

點擊這裏查看大圖
這裏寫圖片描述

RTFRS解釋

public class LruCache<K, V> {

    //正真用於緩存的容器
    //支持訪問順序呢:指在迭代遍歷列表中的元素時最近訪問的元素會排在LinkedHashMap的尾部
    //清除操作是從頭部開始清除的,可以實現“最近最少使用”的需求
    private final LinkedHashMap<K, V> map;

    //當前換粗的大小
    private int size;

    //最大容量
    private int maxSize;

    //put進來的數量
    private int putCount;

    //create出來的數量
    private int createCount;

    //清除的數量
    private int evictionCount;

    //get到值的數量
    private int hitCount;

    //沒有get到值得數量
    private int missCount;

    //構造方法
    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);
    }

    //重新設置最大容量
    public void resize(int maxSize) {
        //異常處理
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        //更新最大容量
        synchronized (this) {
            this.maxSize = maxSize;
        }
        //size的值可能變小,嘗試清除部分緩存
        trimToSize(maxSize);
    }

    //通過key來獲取相應的值
    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的數據
                hitCount++;
                return mapValue;
            }
            //當容器中沒有儲存相應的值,那麼:更新missCount的數據
            missCount++;
        }


        //create一個值,默認爲null
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }
        //重寫create()方法後執行下面的代碼


        synchronized (this) {
            //create出了一個值,那麼:更新createCount的數據
            createCount++;
            //放入容器,map.put()的返回值:
            //1.null,新添加的數據
            //2.不爲null,表示鏈表中存在相應的value,put()表示更新數據,將舊的數據返回
            mapValue = map.put(key, createdValue);

            //對mapValue的判斷表示put()的操作是更新還是添加
            if (mapValue != null) {
                //更新操作,那麼create出來的值不能覆蓋以前的值
                //將舊的值重新放回到容器中
                map.put(key, mapValue);
            } else {
                //添加進來新的值,那麼容器的大小會發生變化,更新size的值
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            //默認什麼都沒有做
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            //size變大,嘗試清除部分緩存
            trimToSize(maxSize);
            return createdValue;
        }
    }

    //添加到緩存中
    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的數據
            putCount++;
            //size變化,更新size的值
            size += safeSizeOf(key, value);
            //添加到容器中
            previous = map.put(key, value);
            //跟get()中的原理一樣,這裏判斷是更新還是添加
            if (previous != null) {
                //更新操作,那麼size多記錄一份舊的value的大小,減去相應的值
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            //默認什麼都沒有做
            entryRemoved(false, key, previous, value);
        }

        //size可能變大,嘗試清除部分緩存
        trimToSize(maxSize);
        return previous;
    }


    //嘗試清除部分緩存
    public void trimToSize(int maxSize) {
        //無限循環,與if (size <= maxSize)配合:可以將緩存清空到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!");
                }

                //當前size沒有達到maxSize,結束循環
                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的數據
                size -= safeSizeOf(key, value);
                //這裏執行了一次清除,那麼,更新evictionCount的值
                evictionCount++;
            }

            //默認什麼都沒有做
            entryRemoved(true, key, value, null);
        }
    }

    //移除相應的緩存
    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的數據
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            //默認什麼都沒有做
            entryRemoved(false, key, previous, null);
        }

        return previous;
    }


    //默認什麼都沒有做;如果有相應的需求,可以重寫該方法
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}


    //create出null;如果有相應的需求,可以重寫該方法
    protected V create(K key) {
        return null;
    }

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

    //開放人員需要重寫的方法,用來標記每個item對應的大小
    protected int sizeOf(K key, V value) {
        return 1;
    }


    //獲取各種成員變量的方法
    //...

    //這裏獲取的是一個map的副本
    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);
    }
}

結語

通過對LruCache的理解,實際上,我們是更加了解了冷門的LinkedHashMap的一些功能:可以記錄訪問順序

轉載請標明出處:http://blog.csdn.net/qq_26411333/article/details/51523790

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章