美文网首页
LruCache最近最少使用原理分析

LruCache最近最少使用原理分析

作者: ModestStorm | 来源:发表于2020-06-11 17:19 被阅读0次

概述

LruCache用于数据缓存中,当缓存所占空间超过了为LruCahce分配的最大空间时,它会回收最近最少使用的元素所占的内存空间,以达到内存平衡的目的。LruCache的底层实现是LinkedHashMap有序双联表。

1.LruCache的创建

 long maxSize=Runtime.getRuntime().totalMemory()/1024;
        int lruCacheSize=(int) maxSize/8;
        LruCache<String, Bitmap> lruCache=new LruCache<String, Bitmap>(lruCacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes()*value.getHeight();
            }
        };

//LinkedHashMap
  public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        //accessOrder为true,代表是最近最少访问策略
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

2.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++;
            //1.首先获取存放key,value时内存空间,累加到目前所占空间size中
            size += safeSizeOf(key, value);
            //2.将key/value保存到LinkedHashMap有序集合中 ,返回集合key对应之前的value
            previous = map.put(key, value);
            //3.如果集合中存在value,所占空间size不变,仅仅更改key对应的value值
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
        //4.如何集合存在对应的value则回调entryRemoved方法,这是空方法,需要开发者自行实现
        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
        //6.调用trimeToSize方法检查是否超出缓存大小,并作出响应
        trimToSize(maxSize);
        return previous;
    }

entryRemoved回调

* @param evicted true if the entry is being removed to make space, false
     *     if the removal was caused by a {@link #put} or {@link #remove}.
     * @param newValue the new value for {@code key}, if it exists. If non-null,
     *     this removal was caused by a {@link #put}. Otherwise it was caused by
     *     an eviction or a {@link #remove}.
     */
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

trimToSize检查缓存是否超越maxSize并清理最近最少使用元素

ate void trimToSize(int maxSize) {
        while (true) {//1.不断循环检测
            K key;
            V value;
            synchronized (this) {//2.参数校验
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
                //3.如果已使用的缓存空间没有达到maxSize阀值则跳出循环
                if (size <= maxSize) {
                    break;
                }

                // BEGIN LAYOUTLIB CHANGE
                // get the last item in the linked list.
                // This is not efficient, the goal here is to minimize the changes
                // compared to the platform version.
                //4.如果已使用的空间达到了maxSize,取出LinkedHashMap中最近最少使用的元素,即链表中最后存放的元素
                Map.Entry<K, V> toEvict = null;
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                }
                // END LAYOUTLIB CHANGE
                //5.如果链表中最后的元素为空则跳出循环
                if (toEvict == null) {
                    break;
                }
                
                /*6.取出链表中最后一个元素调用LinkedHashMap进行删除
                * 并且修改已使用缓存空间size的值
                */
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }
            //7.回调entryRemoved方法交给调用者实现,注意这里返回的evicted为true
            entryRemoved(true, key, value, null);
        }
    }

3.LruCache.get获取缓存数据操作

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

        V mapValue;
        synchronized (this) {
            //1.实际调用的是LinkedHashMap.get(key)方法
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }

LinkedHashMap.get(key)

 public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        //还记得LruCache构造LinkedHashMap传的参数accessOrder=true吗?基于访问顺序
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

LinkedHashMap.afterNodeAccess(e):将调用get访问的元素放到链表头部位置,这就是最近最少使用的原理

  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最近最少使用原理分析

      本文链接:https://www.haomeiwen.com/subject/gcobtktx.html