LinkedHashMap最佳實踐:LruCache

    一句話解釋:LruCache(least recently used cache)最近最少使用緩存。
    前面,我們一起學習了LinkedHashMap數據結構,那麼LruCache就是LinkedHashMap的最佳實踐,童鞋們可以查看我的博客線性表數據結構解讀(六)鏈式哈希表結構-LinkedHashMap學習一下。
    在日常開發中,我們經常會使用一種內存緩存技術,即軟引用或弱引用 (SoftReference or WeakReference)。但是現在已經不再推薦使用這種方式了,因爲從 Android 2.3 (API Level 9)開始,垃圾回收器會更傾向於回收持有軟引用或弱引用的對象,這讓軟引用和弱引用變得不再可靠。另外,Android 3.0 (API Level 11)中,圖片的數據會存儲在本地的內存當中,因而無法用一種可預見的方式將其釋放,這就有潛在的風險造成應用程序的內存溢出並崩潰。
    而谷歌大概從SDK21開始,提供LruCache這個工具類(此類在android-support-v4的包中提供) ,用於作爲實現內存緩存技術的解決方案。這個類非常適合用來緩存圖片,它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,並且把最近最少使用的對象在緩存值達到預設定值之前從內存中移除。

源碼解讀

    OK老規矩,我先帶大家一起研讀下LruCache的源碼,我們重點看下get、put、Remove等方法,其實原理就是LinkedHashMap的機制。

public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;// 聲明一個LinkedHashMap
    private int size;// 已經存儲的數量大小
    private int maxSize;// 規定的最大存儲空間
    private int putCount;// put的次數
    private int createCount;// create的次數
    private int evictionCount;// 回首的次數
    private int hitCount;// 命中的次數
    private int missCount;// 丟失的次數
    /**
     * 指定最大內存的LruCache構造方法
     * @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) {// 官方推薦maxSize一般聲明爲手機內存的1/8
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
    /**
     * Sets the size of the cache.
     * @param maxSize The new maximum size.
     */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }
    /**
     * 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.
     */
    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;
        }
    }

    /**
     * Caches {@code value} for {@code key}. The value is moved to the head of
     * the queue.
     * @return the previous value mapped by {@code key}.
     */
    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;
    }

    /**
     * 移除最老的元素,直到剩餘元素數量等於或小於請求所需的大小
     * Remove the eldest entries until the total of remaining entries is at or
     * below the requested size.
     * @param maxSize the maximum size of the cache before returning. May be -1
     *            to evict even 0-sized elements.
     */
    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 || map.isEmpty()) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

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

    /**
     * 移除已存在的元素實體
     * Removes the entry for {@code key} if it exists.
     * @return the previous value mapped by {@code key}.
     */
    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;
    }
    ……
    }

上面的關於LruCache初始化分配緩存大小有多少,可以參考下面幾個因素:

  • 你的設備可以爲每個應用程序分配多大的內存?
  • 設備屏幕上一次最多能顯示多少張圖片?有多少圖片需要進行預加載,因爲有可能很快也會顯示在屏幕上?
  • 你的設備的屏幕大小和分辨率分別是多少?
  • 圖片的尺寸和大小,還有每張圖片會佔據多少內存空間?
  • 圖片被訪問的頻率有多高?會不會有一些圖片的訪問頻率比其它圖片要高?如果有的話,你也許應該讓一些圖片常駐在內存當中,或者使用多個LruCache 對象來區分不同組的圖片。

基本使用

    Cache保存一個強引用來限制內容數量,每當Item被訪問的時候,此Item就會移動到隊列的頭部。當cache已滿的時候加入新的item時,在隊列尾部的item會被回收。

int cacheSize = 4 * 1024 * 1024; // 4MiB
   LruCache bitmapCache = new LruCache(cacheSize) {
       protected int sizeOf(String key, Bitmap value) {
           return value.getByteCount();

   }
  }

    創建一個大小爲4M的存儲空間來進行圖片的存儲,存儲按照隊列的形式,後存儲進來的和最新使用過的將會放在隊列的最後,這樣陳舊數據放在隊列的開始,用於GC的回收。

   synchronized (cache) {
     if (cache.get(key) == null) { 
         cache.put(key, value);

   }}

    這個方法也展示了怎樣規範化的使用以及獲取由LruCache保存的數據,由於這個類是線程安全的所以需要加上同步塊來進行存放數據,通過get和put方式來進行數據的存取,這點跟Map是一致的,put時如果鍵相同則會進行數據的覆蓋,但是有點需要注意這裏key和value都不能爲空,這裏跟Map有點區別。
    還必須注意必須要主動的釋放資源,如果你cache的某個值需要明確釋放,重寫方法

entryRemoved (boolean evicted, K key, V oldValue, V newValue)

    如果資源是被系統回收的則evicted會返回TRUE,如果是由put,remove的方式替換回收的則evicted會返回FALSE,然後怎麼知道是通過put還是remove的,可以通過對newValue是否爲空進行判斷,如果爲空則是put調用,然後將remove和系統回收時將資源置爲空,就要自己去實現了。
    如果key相對應的item丟掉啦,重寫create().這簡化了調用代碼,即使丟失了也總會返回。默認cache大小是測量的item的數量,重寫sizeof計算不同item的大小。

參考鏈接:http://blog.csdn.net/linghu_java/article/details/8574102

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