我们都知道Android的三级缓存机制:内存缓存、磁盘缓存和网络缓存。其中主要的就是内存缓存和硬盘缓存。这两种缓存机制的实现都应用到了LruCache算法。
三级缓存的大致原理:
- 首次加载App时肯定要先从网络获取数据,然后把数据保存到内存和本地SD卡;
- 当再次加载数据时,首先会从内存缓存中加载,如果内存中的当前数据为空,则再去本地SD卡中加载,如果SD卡中有数据,则展示数据,顺便还会把数据保存到内存中;如果SD卡中也没有当前数据,则再去请求网络。
LRU:Least Recently Used 挨个翻译意思就是:最少、最近、使用。
LruCache缓存机制
LruCache中维护了一个以访问顺序排序的LinkedHashMap,当调用put()方法时,就会在集合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队首元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队尾。
为什么LruCache用LinkHashMap来存储数据?
LinkedHashMap是HashMap和双向链表的结合体,他能够保证插入和取出时的数据顺序的一致性。通过链表来记录元素的顺序和链接关系,通过HashMap来存储数据,它可以控制元素的被遍历时候输出的顺序(按照最近访问顺序来排序,还是按插入顺序)。元素被保存在一个双向链表中,默认的遍历顺序是按插入顺序来被遍历的。通过构造函数的accessOrder来控制是否按照访问顺序来遍历。当以访问顺序来排序的时候,LinkedHashMap总是将最近访问的元素放在队列的尾部,所以第一个元素就是最不经常访问的元素,当容量满了的时候就会将第一个元素移除,凭借get,put,以及putAll都会影响表结构,get的时候需要将当前被访问的这个对象移动到表的最后面。
LruCache源码解析
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//由此可见,LruCache内部维护了一个LinkedHashMap来实现数据的存储、获取和删除功能
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
LruCache的构造方法设置了一个最大容量并调用了LinkedHashMap三个参数的构造方法。这个构造方法的第三个参数就是设置LinkedHashMap是按照访问的顺序来排列元素。
LruCache是如何put数据的?
我们只看关键代码
V previous;
synchronized (this) {
putCount++;
//这里首先让size数量+1,safeSizeOf()方法最终返回的是1
//所以size += safeSizeOf(key, value);其实可以看成是size+=1;
size += safeSizeOf(key, value);
//map.put方法会返回一个当前key对应的value值
previous = map.put(key, value);
if (previous != null) {
//如果当前key对应的value值已经存在,则size把刚才加的1再减掉,相当于size没变
size -= safeSizeOf(key, previous);
}
}
...
...
//关键方法,这个方法就是实现LruCache缓存机制的核心方法
trimToSize(maxSize);
LruCache是如何实现缓存机制的?
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的队首元素
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);
}
}
分析:如果当前size超过了最大限制数,则获取LinkedHashMap队首的元素并删除,此操作无限循环直到当前size小于等于最大限制数。
如何获取一个bitmap的内存大小?
- bitmap的高(bitmap.getHeight(),或者可以说为行数) * bitmap所占的字节数(bitmap.getRowByte())
- 直接调用bitmap.getByteCount()
两者区别:bitmap.getRowByte()所支持的版本更低,而bitmap.getByteCount()内存的实现其实就是bitmap.getRowByte() * bitmap.getHeight();