Android缓存机制——LruCache的详解
概述
lrucache的核心原理就是对linkedhashmap的有效利用,它的内部存在一个linkedhashmap成员变量,值得注意的4个方法:构造方法、get、put、trimtosize
lru(least recently used)缓存算法便应运而生,lru是最近最少使用的算法,它的核心思想是当缓存满时,会优先淘汰那些最近最少使用的缓存对象。采用lru算法的缓存有两种:lrhcache和dislrucache,分别用于实现内存缓存和硬盘缓存,其核心思想都是lru缓存算法。
lru原理
lrucache的核心思想很好理解,就是要维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队头,即将被淘汰。而最近访问的对象将放在队尾,最后被淘汰。(队尾添加元素,队头删除元素)
lrucache 其实使用了 linkedhashmap 双向链表结构,现在分析下 linkedhashmap 使用方法。
1.构造方法:
public linkedhashmap(int initialcapacity, float loadfactor, boolean accessorder) { super(initialcapacity, loadfactor); this.accessorder = accessorder; }
当 accessorder 为 true 时,这个集合的元素顺序就会是访问顺序,也就是访问了之后就会将这个元素放到集合的最后面。
例如:
linkedhashmap < integer, integer > map = new linkedhashmap < > (0, 0.75f, true); map.put(0, 0); map.put(1, 1); map.put(2, 2); map.put(3, 3); map.get(1); map.get(2); for (map.entry < integer, integer > entry: map.entryset()) { system.out.println(entry.getkey() + ":" + entry.getvalue()); }
输出结果:
0:0
3:3
1:1
2:2
下面我们在lrucache源码中具体看看,怎么应用linkedhashmap来实现缓存的添加,获得和删除的:
/** * @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);//accessorder被设置为true }
从lrucache的构造函数中可以看到正是用了linkedhashmap的访问顺序。
2.put()方法
/** * caches {@code value} for {@code key}. the value is moved to the head of * the queue. * * @return the previous value mapped by {@code key}. */ 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++;//插入缓存对象加1 size += safesizeof(key, value);//增加已有缓存的大小 previous = map.put(key, value);//向map中加入缓存对象 if (previous != null) {//如果已有缓存对象,则缓存大小恢复到之前 size -= safesizeof(key, previous); } } if (previous != null) {//entryremoved()是个空方法,可以自行实现 entryremoved(false, key, previous, value); } trimtosize(maxsize);//调整缓存大小(关键方法) return previous; }
可以看到put()方法重要的就是在添加过缓存对象后,调用 trimtosize()方法来保证内存不超过maxsize
3.trimtosize方法
再看一下trimtosize()方法:
/** * remove the eldest entries until the total of remaining entries is at or * below the requested size. * * @param maxsize the maximum size of the cache before returning. may be -1 * to evict even 0-sized elements. */ public void trimtosize(int maxsize) { while (true) {//死循环 k key; v value; synchronized (this) { //如果map为空并且缓存size不等于0或者缓存size小于0,抛出异常 if (size < 0 || (map.isempty() && size != 0)) { throw new illegalstateexception(getclass().getname() + ".sizeof() is reporting inconsistent results!"); } //如果缓存大小size小于最大缓存,或者map为空,不需要再删除缓存对象,跳出循环 if (size <= maxsize) { break; } // 取出 map 中最老的映射 map.entry<k, v> toevict = map.eldest(); if (toevict == null) { break; } key = toevict.getkey(); value = toevict.getvalue(); map.remove(key); size -= safesizeof(key, value); evictioncount++; } entryremoved(true, key, value, null); } }
trimtosize()方法不断地删除linkedhashmap中队头的元素,即近期最少访问的,直到缓存大小小于最大值。
4. get方法
当调用lrucache的get()方法获取集合中的缓存对象时,就代表访问了一次该元素,将会更新队列,保持整个队列是按照访问顺序排序。这个更新过程就是在linkedhashmap中的get()方法中完成的。
接着看lrucache的get()方法
/** * returns the value for {@code key} if it exists in the cache or can be * created by {@code #create}. if a value was returned, it is moved to the * head of the queue. this returns null if a value is not cached and cannot * be created. */ public final v get(k key) { if (key == null) {//key不能为空 throw new nullpointerexception("key == null"); } v mapvalue; synchronized (this) { /获取对应的缓存对象 mapvalue = map.get(key); if (mapvalue != null) { hitcount++; return mapvalue; } misscount++; }
看到lrucache的get方法实际是调用了linkedhashmap的get方法:
public v get(object key) { linkedhashmapentry<k,v> e = (linkedhashmapentry<k,v>)getentry(key); if (e == null) return null; e.recordaccess(this);//实现排序的关键 return e.value; }
再接着看linkedhashmapentry的recordaccess方法:
/** * this method is invoked by the superclass whenever the value * of a pre-existing entry is read by map.get or modified by map.set. * if the enclosing map is access-ordered, it moves the entry * to the end of the list; otherwise, it does nothing. */ void recordaccess(hashmap<k,v> m) { linkedhashmap<k,v> lm = (linkedhashmap<k,v>)m; if (lm.accessorder) {//判断是否是访问顺序 lm.modcount++; remove();//删除此元素 addbefore(lm.header);//将此元素移到队尾 } }
recordaccess方法的作用是如果accessorder为true,把已存在的entry在调用get读取或者set编辑后移到队尾,否则不做任何操作。
也就是说: 这个方法的作用就是将刚访问过的元素放到集合的最后一位
总结:
lrucache的核心原理就是对linkedhashmap 对象的有效利用。在构造方法中设置maxsize并将accessorder设为true,执行get后会将访问元素放到队列尾,put操作后则会调用trimtosize维护linkedhashmap的大小不大于maxsize。
以上所述是小编给大家介绍的android缓存机制lrucache详解整合,希望对大家有所帮助
下一篇: Android开心消消乐代码实例详解