Android缓存机制——LruCache

Android的三级缓存,其中主要的就是内存缓存和硬盘缓存。这两种缓存机制的实现都应用到了LruCache算法,下面我们就从使用到源码解析,来彻底理解Android中的缓存机制。

LruCache 介绍

LruCache 顾名思义就是使用LRU缓存策略的缓存,那么LRU是什么呢? 最近最少使用到的(least recently used),就是当超出缓存容量的时候,就优先淘汰链表中最近最少使用的那个数据。

讲到LruCache,其实最关键的还是LinkedHashMap。LruCache的源码本身很少,而实现了LRU的功能都是靠LinkedHashMap。
 

LruCache 使用
// 初始化LruCache
int memory = (int) Runtime.getRuntime().totalMemory() / 1024;
int cache = memory / 8;
LruCache lruCache = new android.util.LruCache<String, Bitmap>(cache) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
    }
};


// 存
lruCache.put("key", value);
// 取
lruCache.get("key");
LruCache 初始化

LruCache初始化,主要初始化一个 LinkedHashMap ,可以看到比平常我们初始化多一个 accessOrder = true 的过程
accessOrder = true 表示: LinkedHashMap 是按操作顺序排序的,put, get 操作都会把当前元素方法队尾

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);
}
LruCache 存元素
public final V put(@NonNull K key, @NonNull V value) {
    if (key == null || value == null) {
        throw new NullPointerException("key == null || value == null");
    }

    V previous;
    synchronized (this) {
        putCount++;
        // 把当前消耗的size加上当前value的size,safeSizeOf 其实就是继承的 sizeOf 方法计算得到的内存大小
        size += safeSizeOf(key, value);
        // 把元素加入到 LinkedHashMap 当中,返回值 previous 表示当前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;
}

// 就是为了防止计算的内存小于0出错,其实就是计算内存的
private int safeSizeOf(K key, V value) {
    int result = sizeOf(key, value);
    if (result < 0) {
        throw new IllegalStateException("Negative size: " + key + "=" + value);
    }
    return result;
}

// 如果要计算真实内存的size,必须自己继承计算
protected int sizeOf(@NonNull K key, @NonNull V value) {
    return 1;
}


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);
    }
}
LruCache 取元素
public final V get(@NonNull K key) {
    if (key == null) {
        throw new NullPointerException("key == null");
    }

    V mapValue;
    synchronized (this) {
    	// 从 LinkedHashMap 中取当前key的元素
        mapValue = map.get(key);
        if (mapValue != null) {
            hitCount++;
            // 如果有直接返回
            return mapValue;
        }
        missCount++;
    }

    // 如果没有,尝试通过 create 方法恢复元素,需要自己继承实现 create 方法
    V createdValue = create(key);
    if (createdValue == null) {
        return null;
    }

    synchronized (this) {
        createCount++;
        // create 方法恢复出来,放入 LinkedHashMap 当中
        mapValue = map.put(key, createdValue);

        // 后续方法和put方法类似
        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;
    }
}
为什么 LinkedHashMap 的 get 方法可以把元素方法队尾

可以看到,当 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;
}


afterNodeAccess 方法,其实就是把元素 e 方法队尾。
 

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMapEntry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMapEntry<K,V> p =
            (LinkedHashMapEntry<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 注意事项
多个继承方法

如果要计算真实内存的size,必须自己继承计算内存size的方法 sizeOf ,不然默认每个对象为 size + 1,不会计算真实内存大小。

如果想某个key的value被回收之后不为空,可以继续使用,必须继承 create 方法,自己在当前key获得的value为空之后,重新创建一个。

如果想要知道某个元素被移除了,可以继承 entryRemoved 方法。
 

找对正确的 LruCache

我看得到是android Q (29) 的源码
android.util.LruCache 中的 trimToSize() 方法,去掉的是队尾。开始我也被搞蒙了很久,LinkedHashMap 不是最近的放在队尾的吗?
后面找了一下,还有一个 android.collection.LruCache ,人家取的就是队列第一个元素。没搞明白是不是 android.util.LruCache 里面错了。
反正后面也找了其他地方得 LruCache ,比如Gilde等,清一色都是去掉的队列第一个元素。
看网上以前android版本的 android.util.LruCache 删的也是第一个元素,不知道什么版本开始删的队尾,有兴趣的可以自己找下。

Map.Entry<K, V> toEvict = null;
for (Map.Entry<K, V> entry : map.entrySet()) {
    toEvict = entry;
}
// END LAYOUTLIB CHANGE

if (toEvict == null) {
    break;
}

key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;


android.collection.LruCache 中的 trimToSize() 方法,去掉的是队列第一个元素

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


————————————————
版权声明:本文为CSDN博主「吴中乐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_16927853/article/details/102905637

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值