安卓开发学习之LruCache源码阅读

背景

LruCache是最近最久未使用的缓存,是安卓系统里常见的缓存策略之一。

源码阅读

LruCache类里定义的属性如下

    private final LinkedHashMap<K, V> map; // 内存对象,哈希链表

    private int size; // 目前使用的内存数
    private int maxSize; // 最大内存数

    private int putCount; // 添加到map队列的操作数
    private int createCount; // 添加的空白内存的数量
    private int evictionCount; // 被lru算法删除(而不是用户手动调用删除)的内存数量
    private int hitCount; // 根据键找值时找到的次数
    private int missCount; // 找不到值的次数

原来就是封装了一个哈希链表,然后记录各种数量。于是我顺着构造方法、增加、查找、删除、清空以及其他方法的顺序进行源码阅读

构造方法

构造方法代码如下,传入了一个maxSize参数

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

保存最大内存数量,然后实例化map,但是map初始长度是0,增长因子是0.75(也就是当添加一个元素进去时,发现当前链表长度达到或超过链表最大长度的75%时,会先扩容到原来的两倍,再添加新元素),第三个参数表示映射链表的排序方式,true表示按照访问顺序排列,false表示按照插入顺序排序,这里要实现最近最久未使用,自然要按照访问顺序排列映射链表,把不常用的放到链表头部,常用的放到链表尾部。

 

添加方法

给缓存里添加元素的方法代码如下,实际是对映射链表的操作

    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++; // 移入数量+1
            size += safeSizeOf(key, value); // size++
            previous = map.put(key, value); // 把新的键值对插入,返回键对应的老值
            if (previous != null) {
                size -= safeSizeOf(key, previous); // 如果以前有值,size不应该变化
            }
        }

        if (previous != null) { // 回调,空实现
            entryRemoved(false, key, previous, value);
        }

        trimToSize(maxSize); // 保证size不大于maxSize
        return previous;
    }

这个方法前面的都比较容易理解,而最后的trimToSize()方法则是用来剔除链表首部那些不常用的元素的,剔除的幅度是保证当前映射链表长度不大于传入的参数。我们看一下trimToSize()方法

    public void trimToSize(int maxSize) {
        // 从头结点开始移除,直到size不大于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 = map.eldest(); // 直接返回头结点
                if (toEvict == null) {
                    break; // 头结点是空,链表就是空,退出循环,结束方法
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value); // size--
                evictionCount++; // 移除数量++
            }

            entryRemoved(true, key, value, null); // 空实现
        }
    }

获取元素的方法

获取元素的方法是get()方法,代码如下

    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); // 默认返回null
        if (createdValue == null) {
            return null; // 返回null
        }

        // 如果某个类覆写了create()方法,让它返回不是null
        synchronized (this) {
            createCount++; // 创造数+1
            mapValue = map.put(key, createdValue); // 先尝试把新值插进去,获得老值

            // 如果老值不为空,就不采用新值,还把老值插回去,否则size++
            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue); // size++
            }
        }

        if (mapValue != null) { // 老值不是null,返回
            entryRemoved(false, key, createdValue, mapValue); // 空实现
            return mapValue;
        } else { // 否则就是插入了新值,返回新值
            trimToSize(maxSize); // 同时执行lru算法,清除头结点
            return createdValue;
        }
    }

删除方法

删除方法是remove(),代码如下

    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key); // 根据键获取值
            if (previous != null) {
                size -= safeSizeOf(key, previous); // size--
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, null); // 空实现
        }

        return previous;
    }

清空方法

清空方法是evictAll()方法,代码如下

    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

给trimToSize()传入的参数是-1,那么trimToSize()方法会从链表首位开始清除,直到映射链表的大小是0为止,当然就清空了

其他方法

重置大小

resize()方法,代码如下

    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

其实就是改变一下maxSize,然后调用trimToSize()方法执行lru以适应新的容量

快照方法

snapShot()方法,代码如下

    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }

就是获取一个map的副本出去

结语

安卓里的LruCache类就是这样,内部核心是维护了一个映射链表,以及实现了一个trimToSize()方法,这个方法也是lru的实现。

以上源码来自Android8.0,sdk26

猜你喜欢

转载自blog.csdn.net/qq_37475168/article/details/81171814