LRU(Least Recently Used) 最近最少使用算法,或命名为最近最久末被使用算法。 意思就是到当前时间为止最少使用算法,它的核心思想就是会优先淘汰那些近期最少使用的缓存对象。
一、LRU 要解决以下几个问题:
1、LRU缓存 需要 提供 添加、访问、删除 的方法
2、在第1步的几个方法中,需要根据当前LRU的总容量,以及本次操作的元素的大小,来决定是否需要移除最少使用的元素。
3、如何确定谁是最少使用的元素
andrid-27的源码中就自带了一个 LRU的实现: android.util.LruCache<K, V>
它内部采用的数据结果是 LinkedHashMap<K, V> , 这是个 双向链表,
构造方法:
public LinkedHashMap(int initialCapacity,
loat loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
二、为什么要使用LinkedHashMap?
当 accessOrder 为 true 时,这个集合的元素顺序就会是访问顺序,也就是访问了之后就会将这个元素放到集合的最后面。这样近期被使用的过元素总是在链表的末尾,那么 最久末被使用过的元素都保存在链表表头, 当缓存容量超过最大容量限制时,只需要直接移除表头的元素就行,大大提高了效率。
LinkedHashMap 的 get 方法如下,当 accessOrder 为true时,它会将当前访问的元素 移动到链表末尾, 链表的操作就不多说了,基本的数据结构知识还是要有的,大家自己看源码.
LinkedHashMap.java 源码
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
afterNodeAccess 源码的方法的命名也很好,翻译过来就是 “访问完节点之后” 的动作,那自然就是把访问的节点移动到链表末尾。
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
三、为什么不使用其它数据结构?
如果使用其它普通数据结构,如ArrayList,那么就需要自己记录每一个元素的访问时间,每次操作时都来查找 之前记录的访问时间,找到时间最早的记录来删除, 这样就降低了处理的效率。
四、如何确定单个元素的大小?
在sdk自带的 LruCache中 ,sizeOf 的计算方法 默认返回了 1, 因为它的key和value 都是泛型的, 它并不知道具体的value 类型,那就无法知道 value所占用的 size的大小了。 我们业务层要使用这个内存LRU缓存时,需要继承sdk中的 LruCache 并重写 sizeOf方法。
/**
* Returns the size of the entry for {@code key} and {@code value} in
* user-defined units. The default implementation returns 1 so that size
* is the number of entries and max size is the maximum number of entries.
*
* <p>An entry's size must not change while it is in the cache.
*/
protected int sizeOf(K key, V value) {
return 1;
}
五、应用场景
例如实现 图片内存Lru缓存功能时, ImageLruCaceh extend LruCaceh<String, Bitmap> , 重写sizeOf 返回单个图片所占用的 内存字节大小。
new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
六、LinkedHashMap
LinkedHashMap增加了时间和空间上的开销,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序。
关 注 点 | 结 论 |
LinkedHashMap是否允许空 | Key和Value都允许空 |
LinkedHashMap是否允许重复数据 | Key重复会覆盖、Value允许重复 |
LinkedHashMap是否有序 | 有序 |
LinkedHashMap是否线程安全 | 非线程安全 |