最近最少使用算法 - 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++的数量毫无关系。

面试题:最近最少使用算法是使用次数多的还是最近的数据先被移除?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容