Lrucahe源码解析(转)

来源:互联网 发布:制作搞笑图片软件 编辑:程序博客网 时间:2024/05/16 12:04
上一篇分析了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方法,用于计算每个条目的大小。

0 0
原创粉丝点击