最近最少使用算法 - LruCache

簡介

我們在做圖片三級緩存時,內存緩存為了防止內存溢出,導致APP崩潰,使用LruCache<K, V>來管理內存數據,內部由最近最少使用算法實現,將內存控制在一定的大小內,超出最大值時會自動回收。

原理

初始化時指定最大內存大小,google原生OS的默認值是16M,但是各個廠家的OS會對這個值進行修改,有的128 有的256等等。可使用val maxMemory = Runtime.getRuntime().maxMemory()來動態獲取手機的內存大小,一般控制在總內存的1/8。

初始化
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);
}
new LruCache<String,Bitmap>((int) maxMemory/8)
添加數據

當有新數據添加時,往LinkedHashMap里面添加數據存儲到鏈表尾端,如果之前存在則移除之前的數據,添加完成之后計算是否超過最大內存。

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

判斷內存集合是否超過最大限制;如果超過,將鏈表頭部的對象也就是近期最少用到的數據移除,直到內存大小滿足最大初始值。

private 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) {
                break;
            }
            Map.Entry<K, V> toEvict = null;
            for (Map.Entry<K, V> entry : map.entrySet()) {
                toEvict = entry;
            }
            if (toEvict == null) {
                break;
            }
            key = toEvict.getKey();
            value = toEvict.getValue();
            map.remove(key);
            size -= safeSizeOf(key, value);
            evictionCount++;
        }
        entryRemoved(true, key, value, null);
}
獲取數據

獲取數據時,有數據時,直接返回數據,沒有數據就調用create返回自定義的或者null

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

    V createdValue = create(key);
    if (createdValue == null) {
        return null;
    }
    synchronized (this) {
        createCount++;
        mapValue = map.put(key, createdValue);
        if (mapValue != null) {
            map.put(key, mapValue);
        } else {
            size += safeSizeOf(key, createdValue);
        }
    }
    if (mapValue != null) {
        entryRemoved(false, key, createdValue, mapValue);
        return mapValue;
    } else {
        trimToSize(maxSize);
        return createdValue;
    }
}

初始化LinkedHashMap accessOrder傳值為true,調用afterNodeAccess;

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

當accessOrder的值為true,且e不是尾節點,將e移到鏈表的尾端;

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<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;
    }
}

LruCache 局部同步鎖
在 get, put, trimToSize, remove 四個方法里的 entryRemoved 方法都不在同步塊里,因為 entryRemoved 回調的參數都屬于方法域參數,不會線程不安全。

總結

通過以上源碼的分析,可以知道新增數據和獲取緩存時,數據都會移動到鏈表尾端,而當前內存大于設置最大內存的大小時,會移除鏈表頭端的數據,與hitCount++的數量毫無關系。

面試題:最近最少使用算法是使用次數多的還是最近的數據先被移除?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容