Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> LinkedHashMap最佳實踐:LruCache

LinkedHashMap最佳實踐:LruCache

編輯:關於Android編程

一句話解釋:LruCache(least recently used cache)最近最少使用緩存。
前面,我們一起學習了LinkedHashMap數據結構,那麼LruCache就是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 {
    private final LinkedHashMap 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(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 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的大小。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved