天天看點

LruCache源碼分析

LruCache的基本使用

在使用LruCache時,一般需要重寫sizeOf方法,該方法用于傳回一個對象所占用的記憶體大小

//擷取系統配置設定給每個應用程式的最大記憶體
int maxMemory=(int)(Runtime.getRuntime().maxMemory()/1024);
int cacheSize=maxMemory/8; 
private LruCache<String, Bitmap> mMemoryCache;
//給LruCache配置設定1/8 
mMemoryCache = new LruCache<String, Bitmap>(mCacheSize){  
    //重寫該方法,來測量Bitmap的大小  
    @Override  
    protected int sizeOf(String key, Bitmap value) {  
        return value.getRowBytes() * value.getHeight()/1024;  
    }  
};           

LruCache的構造函數

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);
}           

可以看到c的本質是維護了一個LinkedHashMap,我們看下LinkedHashMap的構造函數的第三個參數的含義

/**
 * Constructs an empty <tt>LinkedHashMap</tt> instance with the
 * specified initial capacity, load factor and ordering mode.
 *
 * @param  initialCapacity the initial capacity  初始化大小
 * @param  loadFactor      the load factor  加載因子
 * @param  accessOrder     the ordering mode - <tt>true</tt> for 
 *         access-order, <tt>false</tt> for insertion-order
 * //如果是true代表是通路順序,如果是false代表插入順序
 * @throws IllegalArgumentException if the initial capacity is negative
 *         or the load factor is nonpositive
 */
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}           

LruCache是通過維護一個根據通路順序排序的LinkedHashMap

put方法

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++;
        //safeSizeOf調用是sizeOf,需要我們重寫,用于計算對象的大小
        size += safeSizeOf(key, value);//總的大小加上新的
        previous = map.put(key, value);//調用LindedHashMap的put,向map中加入緩存對象,若對象已存在,傳回已有的值,如果不存在則傳回null
        if (previous != null) {
            size -= safeSizeOf(key, previous);//如果對象已經存在,需要減掉上一個的
        }
    }
    // entryRemoved預設是空實作,我們根據需要自行實作,用于對象删除之後的操作
    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }
    // 調整緩存的大小
    trimToSize(maxSize);
    return previous;
}           

put方法主要是調用LindedHashMap的put來儲存資料,調整緩存大小。并調用trimToSize來判斷緩存是否已經滿了,如果大于了最大的緩存大小,通過循環不斷删除最近最少使用的對象,直到緩存大小小于最大的緩存大小

trimToSize方法

public void trimToSize(int maxSize) {
    while (true) {
        K key;
        V value;
        synchronized (this) {
            // 如果緩存小于0或者map為空而緩存大小不為0,抛出異常
            if (size < 0 || (map.isEmpty() && size != 0)) {
                throw new IllegalStateException(getClass().getName()
                        + ".sizeOf() is reporting inconsistent results!");
            }
            //沒有超過最大的緩存大小,不需要做任何操作,直接跳出循環
            if (size <= maxSize) {
                break;
            }
            //從緩存map中找到最近最少使用的對象,如果不存在直接跳出循環
            Map.Entry<K, V> toEvict = map.eldest();
            if (toEvict == null) {
                break;
            }
            key = toEvict.getKey();
            value = toEvict.getValue();
            map.remove(key);//map中删除這個對象
            size -= safeSizeOf(key, value);//調整目前緩存大小
            evictionCount++;//回收次數+1
        }
        //預設空實作
        entryRemoved(true, key, value, null);
    }
}           

trimToSize主要維護緩存size的大小,確定緩存小于最大的緩存值。一旦發現大于最大緩存值,調用map的eldest方法找到最近最少使用的對象,并删除。eldest是擷取LinkedHashMap的隊首的元素。

get方法

public final V get(K key) {
    if (key == null) {// 參數合法性檢查
        throw new NullPointerException("key == null");
    }
    V mapValue;
    synchronized (this) {
        mapValue = map.get(key);//根據key的到對象的value
        //如果對象存在,則傳回這個value
        if (mapValue != null) {
            hitCount++;//命中數+1
            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.
     * 如果通過key從緩存集合中擷取不到緩存資料,則嘗試使用creat方法創造一個value。
     * create預設實作是傳回null,需要的時候可以重寫這個方法
     */
    V createdValue = create(key);
    if (createdValue == null) {
        return null;
    }
    // 如果建立成功,則将新的value添加到map中,跟put方法類似
    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;
    }           

LruCache的get方法,從集合中擷取緩存,本質是調用LinkedHashMap的get,每調用一次get就代表通路了一次該元素,将會更新隊列,保持整個隊列是按照通路順序排序。下面是LinkedHashMap的get的實作

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);//将e移到隊尾,保證按照通路順序排序
    return e.value;
}           

總結

LruCache中維護了一個集合LinkedHashMap,LinkedHashMap是以通路順序排序的。

主要的核心方法put、get和trimToSize。put和get是調用LinkedHashMap的put和get。通過trimToSize確定緩存小于最大緩存值。

繼續閱讀