zoukankan      html  css  js  c++  java
  • 【源代码】LruCache源代码剖析

    上一篇分析了LinkedHashMap源代码,这个Map集合除了拥有HashMap的大部分特性之外。还拥有链表的特点,即能够保持遍历顺序与插入顺序一致。

    另外。当我们将accessOrder设置为true时。能够使遍历顺序和訪问顺序一致,其内部双向链表将会依照最近最少訪问到最近最多訪问的顺序排列Entry对象,这能够用来做缓存。


    这篇文章分析的LruCache并非jdk中的类,而是来自安卓,熟悉安卓内存缓存的必定对这个类不陌生。

    LruCache内部维护的就是一个LinkedHashMap。
    以下開始分析LruCache。
    注:以下LruCache源代码来自support.v4包。

    首先是这个类的成员变量:
     private final LinkedHashMap<K, V> map;
        /** Size of this cache in units. Not necessarily the number of elements. */
        private int size;//当前大小
        private int maxSize;//最大容量
        private int putCount;//put次数
        private int createCount;//create次数
        private int evictionCount;//回收次数
        private int hitCount;//命中次数
        private int missCount;//丢失次数
    LinkedHashMap的初始化放在构造器中:
    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);
        }

    这里将LinkedHashMap的accessOrder设置为true。

    接下来看两个最重要的方法,put和get。

    首先是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) {//之前已经插入过同样的key
                    size -= safeSizeOf(key, previous);//那么减去该entry的容量,由于发生覆盖
                }
            }
            if (previous != null) {
                entryRemoved(false, key, previous, value);//这种方法默认空实现
            }
            trimToSize(maxSize);//若容量超过maxsize,将会删除近期非常少訪问的entry
            return previous;
        }
    put方法无非就是调用LinkedHashMap的put方法。可是这里在调用LinkedHashMap的put方法之前,推断了key和value是否为空,也就是说LruCache不同意空键值

    除此之外,put操作被加锁了,所以是线程安全的

    既然是缓存,那么必定可以动态删除一些不经常使用的键值对,这个工作是由trimToSize方法完毕的:
     public void trimToSize(int maxSize) {
            while (true) {//不断删除linkedHashMap头部entry,也就是近期最少訪问的条目。直到size小于最大容量
                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.Entry<K, V> toEvict = map.entrySet().iterator().next();//指向链表头
                    key = toEvict.getKey();
                    value = toEvict.getValue();
                    map.remove(key);//删除最少訪问的entry
                    size -= safeSizeOf(key, value);
                    evictionCount++;
                }
                entryRemoved(true, key, value, null);
            }
        }

    这种方法不断循环删除链表首部元素。也就是近期最少訪问的元素,直到容量不超过预先定义的最大值为止。
    注:LruCache在android.util包中也有一个LruCache类,可是我发现这个类的trimToSize方法是错误的:
    private 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 = null;
                    for (Map.Entry<K, V> entry : map.entrySet()) {
                        toEvict = entry;
                    }
        
                    if (toEvict == null) {
                        break;
                    }
                    key = toEvict.getKey();
                    value = toEvict.getValue();
                    map.remove(key);
                    size -= safeSizeOf(key, value);
                    evictionCount++;
                }
                entryRemoved(true, key, value, null);
            }
        }
    这里的代码将会循环删除链表尾部,也就是近期訪问最多的元素,这是不对的!所以大家在做内存缓存的时候一定要注意,看trimToSize方法是否有问题。

    接下来是get方法:
    public final V get(K key) {
            if (key == null) {//不同意空键
                throw new NullPointerException("key == null");
            }
            V mapValue;
            synchronized (this) {//线程安全
                mapValue = map.get(key);//调用LinkedHashMap的get方法
                if (mapValue != null) {
                    hitCount++;//命中次数加1
                    return mapValue;//返回value
                }
                missCount++;//未命中
            }
    
            V createdValue = create(key);//默认返回为false
            if (createdValue == null) {
                return null;
            }
            synchronized (this) {
                createCount++;//假设创建成功,那么create次数加1
                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;
            }
        }

    get方法即依据key在LinkedHashMap中寻找相应的value,此方法也是线程安全的。

    以上就是LruCache最重要的部分,以下再看下其它方法:
    remove:
      public final V remove(K key) {
            if (key == null) {
                throw new NullPointerException("key == null");
            }
            V previous;
            synchronized (this) {
                previous = map.remove(key);//调用LinkedHashMap的remove方法
                if (previous != null) {
                    size -= safeSizeOf(key, previous);
                }
            }
            if (previous != null) {
                entryRemoved(false, key, previous, null);
            }
            return previous;//返回value
        }

    sizeof:这种方法用于计算每一个条目的大小,子类必须得复写这个类。
     protected int sizeOf(K key, V value) {//用于计算每一个条目的大小
            return 1;
        }
    snapshot方法,返回当前缓存中全部的条目集合
     public synchronized final Map<K, V> snapshot() {
            return new LinkedHashMap<K, V>(map);
        }

    总结:
    1.LruCache封装了LinkedHashMap。提供了LRU缓存的功能;
    2.LruCache通过trimToSize方法自己主动删除近期最少訪问的键值对。
    3.LruCache不同意空键值;
    4.LruCache线程安全。
    5.继承LruCache时,必需要复写sizeof方法。用于计算每一个条目的大小。






  • 相关阅读:
    [JLOI2011]飞行路线 不同的算法,不同的悲伤
    洛谷 [USACO17OPEN]Bovine Genomics G奶牛基因组(金) ———— 1道骗人的二分+trie树(其实是差分算法)
    P3203 [HNOI2010]弹飞绵羊 —— 懒标记?分块?
    tarjan 题目汇总(含解析)
    P1262 间谍网络 (tarjan缩点 水过去)
    #1117. 编码 ( 字典树版 ) 题解分析
    一道并查集的(坑)题:关闭农场closing the farm
    四重解法---P1047 校门外的树
    一道搜索题【2013 noip提高组 DAY2 t3】华容道
    ●BZOJ 1499 [NOI2005]瑰丽华尔兹
  • 原文地址:https://www.cnblogs.com/lytwajue/p/7202120.html
Copyright © 2011-2022 走看看