【源码】LruCache源码剖析

来源:互联网 发布:win 2008 如何打开端口 编辑:程序博客网 时间:2024/04/27 08:04
上一篇分析了LinkedHashMap源码,这个Map集合除了拥有HashMap的大部分特性之外,还拥有链表的特点,即可以保持遍历顺序与插入顺序一致。另外,当我们将accessOrder设置为true时,可以使遍历顺序和访问顺序一致,其内部双向链表将会按照近期最少访问到近期最多访问的顺序排列Entry对象,这可以用来做缓存。

这篇文章分析的LruCache并不是jdk中的类,而是来自安卓,熟悉安卓内存缓存的必然对这个类不陌生。
LruCache内部维护的就是一个LinkedHashMap。
下面开始分析LruCache。
注:下面LruCache源码来自support.v4包。
首先是这个类的成员变量:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private final LinkedHashMap<K, V> map;  
  2.    /** Size of this cache in units. Not necessarily the number of elements. */  
  3.    private int size;//当前大小  
  4.    private int maxSize;//最大容量  
  5.    private int putCount;//put次数  
  6.    private int createCount;//create次数  
  7.    private int evictionCount;//回收次数  
  8.    private int hitCount;//命中次数  
  9.    private int missCount;//丢失次数  
LinkedHashMap的初始化放在构造器中:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public LruCache(int maxSize) {  
  2.         if (maxSize <= 0) {  
  3.             throw new IllegalArgumentException("maxSize <= 0");  
  4.         }  
  5.         this.maxSize = maxSize;  
  6.         this.map = new LinkedHashMap<K, V>(00.75f, true);  
  7.     }  

这里将LinkedHashMap的accessOrder设置为true。
接下来看两个最重要的方法,put和get。首先是put方法:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final V put(K key, V value) {  
  2.         if (key == null || value == null) {//键值不允许为空  
  3.             throw new NullPointerException("key == null || value == null");  
  4.         }  
  5.         V previous;  
  6.         synchronized (this) {//线程安全  
  7.             putCount++;  
  8.             size += safeSizeOf(key, value);  
  9.             previous = map.put(key, value);  
  10.             if (previous != null) {//之前已经插入过相同的key  
  11.                 size -= safeSizeOf(key, previous);//那么减去该entry的容量,因为发生覆盖  
  12.             }  
  13.         }  
  14.         if (previous != null) {  
  15.             entryRemoved(false, key, previous, value);//这个方法默认空实现  
  16.         }  
  17.         trimToSize(maxSize);//若容量超过maxsize,将会删除最近很少访问的entry  
  18.         return previous;  
  19.     }  
put方法无非就是调用LinkedHashMap的put方法,但是这里在调用LinkedHashMap的put方法之前,判断了key和value是否为空,也就是说LruCache不允许空键值。除此之外,put操作被加锁了,所以是线程安全的
既然是缓存,那么必然能够动态删除一些不常用的键值对,这个工作是由trimToSize方法完成的:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void trimToSize(int maxSize) {  
  2.        while (true) {//不断删除linkedHashMap头部entry,也就是最近最少访问的条目,直到size小于最大容量  
  3.            K key;  
  4.            V value;  
  5.            synchronized (this) {//线程安全  
  6.                if (size < 0 || (map.isEmpty() && size != 0)) {  
  7.                    throw new IllegalStateException(getClass().getName()  
  8.                            + ".sizeOf() is reporting inconsistent results!");  
  9.                }  
  10.                if (size <= maxSize || map.isEmpty()) {//直到容量小于最大容量为止  
  11.                    break;  
  12.                }  
  13.                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();//指向链表头  
  14.                key = toEvict.getKey();  
  15.                value = toEvict.getValue();  
  16.                map.remove(key);//删除最少访问的entry  
  17.                size -= safeSizeOf(key, value);  
  18.                evictionCount++;  
  19.            }  
  20.            entryRemoved(true, key, value, null);  
  21.        }  
  22.    }  

这个方法不断循环删除链表首部元素,也就是最近最少访问的元素,直到容量不超过预先定义的最大值为止。
注:LruCache在android.util包中也有一个LruCache类,但是我发现这个类的trimToSize方法是错误的:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void trimToSize(int maxSize) {  
  2.         while (true) {  
  3.             K key;  
  4.             V value;  
  5.             synchronized (this) {  
  6.                 if (size < 0 || (map.isEmpty() && size != 0)) {  
  7.                     throw new IllegalStateException(getClass().getName()  
  8.                             + ".sizeOf() is reporting inconsistent results!");  
  9.                 }  
  10.                 if (size <= maxSize) {  
  11.                     break;  
  12.                 }  
  13.               
  14.                 Map.Entry<K, V> toEvict = null;  
  15.                 for (Map.Entry<K, V> entry : map.entrySet()) {  
  16.                     toEvict = entry;  
  17.                 }  
  18.       
  19.                 if (toEvict == null) {  
  20.                     break;  
  21.                 }  
  22.                 key = toEvict.getKey();  
  23.                 value = toEvict.getValue();  
  24.                 map.remove(key);  
  25.                 size -= safeSizeOf(key, value);  
  26.                 evictionCount++;  
  27.             }  
  28.             entryRemoved(true, key, value, null);  
  29.         }  
  30.     }  
这里的代码将会循环删除链表尾部,也就是最近访问最多的元素,这是不正确的!所以大家在做内存缓存的时候一定要注意,看trimToSize方法是否有问题。

接下来是get方法:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final V get(K key) {  
  2.         if (key == null) {//不允许空键  
  3.             throw new NullPointerException("key == null");  
  4.         }  
  5.         V mapValue;  
  6.         synchronized (this) {//线程安全  
  7.             mapValue = map.get(key);//调用LinkedHashMap的get方法  
  8.             if (mapValue != null) {  
  9.                 hitCount++;//命中次数加1  
  10.                 return mapValue;//返回value  
  11.             }  
  12.             missCount++;//未命中  
  13.         }  
  14.   
  15.         V createdValue = create(key);//默认返回为false  
  16.         if (createdValue == null) {  
  17.             return null;  
  18.         }  
  19.         synchronized (this) {  
  20.             createCount++;//如果创建成功,那么create次数加1  
  21.             mapValue = map.put(key, createdValue);//放到哈希表中  
  22.             if (mapValue != null) {  
  23.                 // There was a conflict so undo that last put  
  24.                 map.put(key, mapValue);  
  25.             } else {  
  26.                 size += safeSizeOf(key, createdValue);  
  27.             }  
  28.         }  
  29.         if (mapValue != null) {  
  30.             entryRemoved(false, key, createdValue, mapValue);  
  31.             return mapValue;  
  32.         } else {  
  33.             trimToSize(maxSize);  
  34.             return createdValue;  
  35.         }  
  36.     }  

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

以上就是LruCache最重要的部分,下面再看下其他方法:
remove:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final V remove(K key) {  
  2.       if (key == null) {  
  3.           throw new NullPointerException("key == null");  
  4.       }  
  5.       V previous;  
  6.       synchronized (this) {  
  7.           previous = map.remove(key);//调用LinkedHashMap的remove方法  
  8.           if (previous != null) {  
  9.               size -= safeSizeOf(key, previous);  
  10.           }  
  11.       }  
  12.       if (previous != null) {  
  13.           entryRemoved(false, key, previous, null);  
  14.       }  
  15.       return previous;//返回value  
  16.   }  

sizeof:这个方法用于计算每个条目的大小,子类必须得复写这个类。
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. protected int sizeOf(K key, V value) {//用于计算每个条目的大小  
  2.        return 1;  
  3.    }  
snapshot方法,返回当前缓存中所有的条目集合
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public synchronized final Map<K, V> snapshot() {  
  2.        return new LinkedHashMap<K, V>(map);  
  3.    }  

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

原文地址:http://blog.csdn.net/chdjj/article/details/38701509

0 0
原创粉丝点击