LruCache的用法和源码的详解

LruCache中的Lru指的是LeastRecentlyUsed,也就是近期最少使用算法。即当缓存容器中的数据大小达到指定的最大容量时,Lru会清楚最近使用最少的数据。

为什么要用LruCache?其实使用它的原因有很多,例如我们要做一个加载网络图片的listview时,如果我们不加节制的向服务器请求大量图片,那么对于服务器来说是一个不少的负担,其次,对于用户来说,每次刷新都意味着流量的大量消耗以及长时间等待,所以缓存机制几乎是每个需要联网的App必须做的。

LruCache已经存在于android3.1之后的API中,但为了兼容android3.1之前的版本,推荐使用support v4中提供的Lrucache 。而这个缓存只是一个内存缓存,并不能进行本地缓存,也就是说,如果内存不足,缓存有可能会失效,而且当App重启的时候,缓存会重新开始生效。如果想要进行本地磁盘缓存,推荐使用DiskLruCache,虽然没包含在官方API中,但是官方推荐我们使用,本文暂不讨论。

Lrucache的是使用很简单,最简单的就是实例化一个Lrucache对象,然后使用它的put()方法和get()方法。
例如:

// 构造方法传入当前应用可用最大内存的八分之一
LruCache<String, Bitmap> mLruCache = new LruCache(Runtime.getRuntime().maxMemory() / 1024 / 8);

稍微复杂点的做法可以重写以下几个方法,这样做可以更好的满足你的需要了

        @Override
        // 重写sizeOf方法,并计算返回每个Bitmap对象占用的内存
        protected int sizeOf(String key, Bitmap value) {
            return value.getByteCount() / 1024; //单位是kb
        }

        @Override
        // 当缓存被移除时调用,第一个参数是表明缓存移除的原因,true表示被LruCache移除,false表示被主动remove移除,可不重写
        protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap
                newValue) {
            super.entryRemoved(evicted, key, oldValue, newValue);
        }

        @Override
        // 当get方法获取不到缓存的时候调用,如果需要创建自定义默认缓存,可以在这里添加逻辑,可不重写
        protected Bitmap create(String key) {
            return super.create(key);
        }

关于这几方法的重写我在下面的源码的解读中会指出重写的必要性。首先从Lrucache类的put()方法入手吧

    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;
    }
size += safeSizeOf(key, value);

在加入map之前会计算value的大小,看看这个私有方法的实现

    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }
int result = sizeOf(key, value);

sizeof()方法就是我们开始重写的,目的就是有我们去返回单个value的大小 。
我们重点来看下面的这行代码

previous = map.put(key, value);

map对象是LinkedHashMap extends HashMap

因此put()方法来源是HashMap类 ,我们看看put()方法在HashMap类中的实现

    @Override public V put(K key, V value) {
        if (key == null) {
            return putValueForNullKey(value);
        }

        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                preModify(e);
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }

        // No entry for (non-null) key is present; create one
        modCount++;
        if (size++ > threshold) {
            tab = doubleCapacity();
            index = hash & (tab.length - 1);
        }
        addNewEntry(key, value, hash, index);
        return null;
    }

put方法中会做一个判断,如果table中存在key所对应的value就替换value,如果不存在就加入table中
,这里要关注的是preModify 和addNewEntry这两个方法,因为LinkedHashMap类对两个方法进行了重写,我们就看addNewEntry方法,这个方法中的实现懂了,preModify方法就简单了

    @Override void addNewEntry(K key, V value, int hash, int index) {
        LinkedEntry<K, V> header = this.header;

        // Remove eldest entry if instructed to do so.
        LinkedEntry<K, V> eldest = header.nxt;
        if (eldest != header && removeEldestEntry(eldest)) {
            remove(eldest.key);
        }

        // Create new entry, link it on to list, and put it into table
        LinkedEntry<K, V> oldTail = header.prv;
        LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
                key, value, hash, table[index], header, oldTail);
        table[index] = oldTail.nxt = header.prv = newTail;
    }

这个方法的代码不多,我看的时候还是花了一点时间才看明白,它其实就是实现了一个双向链表
这里写图片描述

根据这图,map对象每加入一个entry时,就会在链表HEAD头节点的邻近右侧加入这个新节点,如果已经存在的entry时当调用put()方法,这个entry对应的节点就会移动到链表的最右侧,换句话说,即离HEAD节点越近,就是近期使用最少的。我们再来看看LinkedHashMap类是怎么重写preModify()方法的

   @Override void preModify(HashMapEntry<K, V> e) {
        if (accessOrder) {
            makeTail((LinkedEntry<K, V>) e);
        }
    }
   private void makeTail(LinkedEntry<K, V> e) {
        // Unlink e
        e.prv.nxt = e.nxt;
        e.nxt.prv = e.prv;

        // Relink e as tail
        LinkedEntry<K, V> header = this.header;
        LinkedEntry<K, V> oldTail = header.prv;
        e.nxt = header;
        e.prv = oldTail;
        oldTail.nxt = header.prv = e;
        modCount++;
    }

而makeTail方法正是去修改了链表的指向 。

再来看看Lrucache类的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++;
        }

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         */

        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

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

我们再看看map对象的 get()方法的在LinkedHashMap类实现

    @Override public V get(Object key) {
        /*
         * This method is overridden to eliminate the need for a polymorphic
         * invocation in superclass at the expense of code duplication.
         */
        if (key == null) {
            HashMapEntry<K, V> e = entryForNullKey;
            if (e == null)
                return null;
            if (accessOrder)
                makeTail((LinkedEntry<K, V>) e);
            return e.value;
        }

        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
                e != null; e = e.next) {
            K eKey = e.key;
            if (eKey == key || (e.hash == hash && key.equals(eKey))) {
                if (accessOrder)
                    makeTail((LinkedEntry<K, V>) e);
                return e.value;
            }
        }
        return null;
    }

同样的,也去调用了makeTail方法将正在使用的entry对应的节点放到链表的最右侧。这里也验证了使用越频繁的节点越靠向链表的右侧,使用少的节点更靠近头节点 。

最后再来讲讲Lrucache容器达到max时是如何清理最少使用entry的

    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) {
                    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);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

这个方法在get(),put(),reSize()中都会有调用,它会判断Lrucache容器当前的大小是否大于maxSize,如果大于就逐个删除靠近Head的节点,否则就退出while循环。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值