Android LruCacha 源码分析
来源:互联网 发布:怎么查看淘宝小号满月 编辑:程序博客网 时间:2024/06/14 17:20
LRU
算法,中文叫 近期最少使用
算法。这个算法的思想是当容量已经满的情况下,我们再次向容器里面存放东西的时候,我们需要先删除之前使用最少的元素,因为这个元素在新元素插入之间一使用的最少,我们认为在后面的过程中,这个元素依然是使用次数最少的,当容积不够的时候,我们当然要删除后面过程中使用次数最少的,腾出空间给新的元素。
Android 里面实现了这种算法,名字叫 LruCache
。下面我们从源码(android sdk 23)的角度来解析这个类
public class LruCache<K, V> { private final LinkedHashMap<K, V> map; // 最重要的一个变量,上面所说的原理就是利用这个 LinkedHashMap 实现的 /** Size of this cache in units. Not necessarily the number of elements. */ private int size; // 已经缓存的元素个数 private int maxSize; // 缓存大小 private int putCount; // 往里面添加元素计数器 private int createCount;// key 为 null 的情况下,默认生成对象的个数 private int evictionCount; // 当加入的元素超过 maxSize 后,调整大小的次数 private int hitCount; // 命中的次数 private int missCount; // 没有命中的次数 /** * @param maxSize for caches that do not override {@link #sizeOf}, this is * the maximum number of entries in the cache. For all other caches, * this is the maximum sum of the sizes of the entries in this cache. */ 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); } // 省略其他代码}
我们先来看看 put
方法是如何工作的
public final V put(K key, V value) { // 如果 key 或 value 为 null 那么抛出异常,说明不接受 null if (key == null || value == null) { throw new NullPointerException("key == null || value == null"); } V previous; // 记录 key 的旧值 synchronized (this) { putCount++; // 插入计数器计数 size += safeSizeOf(key, value); // 修正 size previous = map.put(key, value); // 取出 LinkedHashMap 里面的旧值 if (previous != null) { // 之前的确保存过 size -= safeSizeOf(key, previous); // 修正 size } } if (previous != null) { // 如果之前保存过,那么删掉这个节点,但是看源码发现,entryRemoved 是空 entryRemoved(false, key, previous, value); } // 调整大小,也是最重要的方法之一 trimToSize(maxSize); // 返回之前插入的值 return previous;}
刚才提到了 trimToSize()
,那我们来看看这个方法的具体实现。
public 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!"); } // size(缓存的元素个数) 小于 maxSize(容量) 那么跳出循环 if (size <= maxSize) { break; } // eldest() 取 LinkedHashMap 里面最年老的元素 Map.Entry<K, V> toEvict = map.eldest(); if (toEvict == null) { // 为空说明 LinkedHashMap 里面没有元素了 break; } key = toEvict.getKey(); value = toEvict.getValue(); // 删掉 最近最少使用的 map.remove(key); size -= safeSizeOf(key, value); // 修正 size evictionCount++; // 缩减计数器 } // 删除元素,entryRemoved 是一个空方法 entryRemoved(true, key, value, null); }}
trimToSize()
方法里面 LinkedHashMap.eldest()
方法很重要,LinkedHashMap.eldest()
的作用就是取出最年老,即最近最少使用的元素。如何取到最近最少使用的元素就是 LinkedHashMap 里面的东西了。我们接下来看看 LinkedHashMap 的源码
public class LinkedHashMap<K, V> extends HashMap<K, V> { /** * A dummy entry in the circular linked list of entries in the map. * The first real entry is header.nxt, and the last is header.prv. * If the map is empty, header.nxt == header && header.prv == header. */ transient LinkedEntry<K, V> header; /** * True if access ordered, false if insertion ordered. */ private final boolean accessOrder; /** * LinkedEntry adds nxt/prv double-links to plain HashMapEntry. */ static class LinkedEntry<K, V> extends HashMapEntry<K, V> { LinkedEntry<K, V> nxt; LinkedEntry<K, V> prv; /** Create the header entry */ LinkedEntry() { super(null, null, 0, null); nxt = prv = this; } /** Create a normal entry */ LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next, LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) { super(key, value, hash, next); this.nxt = nxt; this.prv = prv; } } // 省略其他代码}
首先,有一个 LinkedEntry 内部类,继承自 HashMapEntry , 里面有 2 个引用分别指向之前和之后的元素,说明这个是一个双向链表结构。 LinkedEntry<K, V> header
这个变量就是双向链表的表头了。 accessOrder
这个的意思是是否需要排序,这个在后面会用到。我们先看看 eldest()
方法。
public Entry<K, V> eldest() { LinkedEntry<K, V> eldest = header.nxt; return eldest != header ? eldest : null;}
方法很简单,就是取出 header 的 nxt 元素。那为什么 header 的 nxt 袁术就是最近最少使用的元素呢?这个是因为在 put
和 get
过程中,如果 accessOrder
设置为 true 的话,会把对应 key 的 LinkedEntry 节点放到队列的尾部。下面我们通过 get
源码分析下这个过程。
public V get(Object key) { /* * This method is overridden to eliminate the need for a polymorphic * invocation in superclass at the expense of code duplication. */ // 如果 key 为空,做key 为空的特殊处理 if (key == null) { HashMapEntry<K, V> e = entryForNullKey; if (e == null) return null; if (accessOrder) makeTail((LinkedEntry<K, V>) e); return e.value; } // 计算 hash 值 int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; // 遍历,找到 key 对应的元素节点 for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { K eKey = e.key; if (eKey == key || (e.hash == hash && key.equals(eKey))) { if (accessOrder) // accessOrder 如果为 true ,那么就执行将元素节点放到队列尾部的操作 makeTail((LinkedEntry<K, V>) e); return e.value; } } // 如果这个 key 之前没有 put 过,那么就返回 null. return null;}
看代码发现, accessOrder
为 true
的时候,我们才会去做将元素节点放到队列的操作,而在 LruCache 里面,LinkedHashMap 的这个值是 true 的。整个的 LruCache 分析就到这里为止。
最后我们看下 makeTail 的源码
private void makeTail(LinkedEntry<K, V> e) { // Unlink e 断开 e 的链接 e.prv.nxt = e.nxt; e.nxt.prv = e.prv; // Relink e as tail 将 e 链接到队列尾部。所以队列头部就是最早put进去但是一直没使用的 // 也就是所谓的 "最近最少使用" LinkedEntry<K, V> header = this.header; LinkedEntry<K, V> oldTail = header.prv; e.nxt = header; e.prv = oldTail; oldTail.nxt = header.prv = e; modCount++;}
- Android LruCacha 源码分析
- Android源码/框架源码分析
- Android 源码分析
- Android源码分析
- Android源码结构分析
- android launcher源码分析
- Android源码结构分析
- Android源码分析--STK
- android Button源码分析
- Android源码分析--彩信
- Android源码分析
- Android Gallery3D源码分析
- android launcher源码分析 .
- android源码结构分析
- Android RIL源码分析
- Android 源码结构分析
- Android 源码结构分析
- Android 源码结构分析 .
- 淘宝客,tomcat 服务器,将root.txt文件放到您网站的根目录下
- [轻微]WEB服务器启用了OPTIONS方法/如何禁止DELETE,PUT,OPTIONS等协议访问应用程序/tomcat下禁用不安全的http方法
- python chardet模块查看编码
- Ansible 系列之 Ad-Hoc介绍及使用
- EXP-00011: XX.tab does not exist
- Android LruCacha 源码分析
- CentOS 7.0如何配置防火墙
- Python问题: UnicodeEncodeError:'ascii' codec can't encode characters in position
- 代码Review系统Gerrit的搭建
- vue笔记--模板语法--指令
- webuploader 上传 总是报abort错误
- Executor框架
- Android的事件分发
- 《Processing SPARQL queries over distributed RDF graphs》——读书笔记