我们都知道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();