LruCache的底层实现原理

19 阅读2分钟

1.核心思想 (Least Recently Used Cache)

当缓存空间不足时,优先淘汰那些最近最少使用的数据

(Least 是little的最高级)

2.原理

  LruCache<Object,Object> lruCache=new LruCache<>(10);
  lruCache.put("key","value"); 
  lruCache.get("key");

LruCache内部维持了一个LinkedHashMap,参数accessOrder=true,表示会按照元素的访问顺序进行排序, 当我们往链表尾部中插入一个元素时,会检查链表的容量超过设定值,如果超过,就把链表头部(最近最少)的元素删除,当我们访问链表的一个元素时,我们会把这个元素插入到链表的尾部.

2.源码解析

  • 双向链表 image.png

  • 当accessOrder为false时(默认值),LinkedHashMap按照元素的插入顺序进行排序。此时,双向链表的作用主要是记录元素的插入顺序,使得LinkedHashMap能够像List那样按照元素插入的顺序进行迭代。

  • 当accessOrder为true时,LinkedHashMap则会按照元素的访问顺序进行排序。这意味着,每次访问(通过get或put方法)一个元素时,该元素都会被移动到双向链表的末尾。这样,最近使用的元素就会出现在链表的末尾,而最近最少使用的元素则会出现在链表的开头。

  • LruCache使用 ,put方法调用的是HashMap的put方法,newNode和newTreeNode方法是LinkedHashMap重写实现的,使用双向链表保存Node节点,get方法也是调用LinkedHashMap重写的get方法

  • 使用方式

  • 源码实现


 public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        //内部是基于LinkedHashMap实现的
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);//accessOrder=true
    }

 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);
        //LinkedHashMap插入节点,如果插入节点存在,返回旧值,否则返回null
            previous = map.put(key, value); //调用HashMap的put方法
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
        trimToSize(maxSize); //检测是否超过链表容量,如果超过,删除头部节点
        return previous;
    }

 public final V get(K key) {
      mapValue = map.get(key);//调用LinkedHashMap的get方法
      trimToSize(maxSize);//检测是否应该删除头结点
    }

 public void trimToSize(int maxSize) {
        while (true) {
                Map.Entry<K, V> toEvict = map.eldest();//获取双向链表的头节点
               if (size <= maxSize) {
                    break;
                }
                //如果size超过链表最大容量
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key); //删除头结点
                size=size-1;
            }
        }
    }
 //获取双向链表头节点
  public Map.Entry<K, V> eldest() {
        return head;
    }
    //LinkedHashMap获取元素
    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e); //把e移动到双向链表的尾部节点
        return e.value;
    }

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