LruCache
1.变量
private final LinkedHashMap<K, V> map; private int size;//已经存储的数据大小 private int maxSize;//最大存储大小 private int putCount;//调用put的次数 private int createCount;//调用create的次数 private int evictionCount;//收回的次数 private int hitCount;//取出数据的成功次数 private int missCount;//取出数据的丢失次数
2.构造函数
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); }
3.保存到缓存
public final V put(K key, V value) { ... V previous; synchronized (this) { putCount++; size += safeSizeOf(key, value); previous = map.put(key, value); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, value); } trimToSize(maxSize); return previous; }
safeSizeOf()
private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } return result; }
sizeOf()
protected int sizeOf(K key, V value) { return 1; }
trimToSize()
public void trimToSize(int maxSize) { while (true) { K key; V value; synchronized (this) { ... if (size <= maxSize) { break; } 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); } }
entryRemoved()是空函数
4.从缓存中取
public final V get(K key) { ... V mapValue; synchronized (this) { mapValue = map.get(key); if (mapValue != null) { hitCount++; return mapValue; } missCount++; } V createdValue = create(key); if (createdValue == null) { return null; } synchronized (this) { createCount++; 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; } }
其中,map.put/get调用的都是LinkedHashMap中的方法,下面我们来看
LinkedHashMap
1.构造函数
public LinkedHashMap() { init(); accessOrder = false; } public LinkedHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public LinkedHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, false); } public LinkedHashMap( int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); init(); this.accessOrder = accessOrder; } public LinkedHashMap(Map<? extends K, ? extends V> map) { this(capacityForInitSize(map.size())); constructorPutAll(map); }
LruCache构造函数中调用的是
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
init()
@Override void init() { header = new LinkedEntry<K, V>(); }
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; } }
2.获取缓存
LruCache中的put调用的就是HashMap的put,get方法在LinkedHashMap中复写
@Override 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. */ if (key == null) { HashMapEntry<K, V> e = entryForNullKey; if (e == null) return null; if (accessOrder) makeTail((LinkedEntry<K, V>) e); return e.value; } int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; 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) makeTail((LinkedEntry<K, V>) e); return e.value; } } return null; }
makeTail()
private void makeTail(LinkedEntry<K, V> e) { // Unlink e e.prv.nxt = e.nxt; e.nxt.prv = e.prv; // Relink e as tail LinkedEntry<K, V> header = this.header; LinkedEntry<K, V> oldTail = header.prv; e.nxt = header; e.prv = oldTail; oldTail.nxt = header.prv = e; modCount++; }
这样,键值对被剥离出来,放到了链表的尾巴上。这样的结果就是不管用户是调用get()
还是put()
都会将操作的那个实体放在链表的最后位置,那么最不常用的就会放在最首位的下一个节点,对应的实体为header.nex
,也就是放在header
的下一节点.
总结:
-
1.LruCache 是通过 LinkedHashMap 构造方法的第三个参数的
accessOrder=true
实现了LinkedHashMap
的数据排序基于访问顺序 (最近访问的数据会在链表尾部),在容量溢出的时候,将链表头部的数据移除。从而,实现了 LRU 数据缓存机制。 -
**2.**LruCache 在内部的get、put、remove包括 trimToSize 都是安全的(因为都上锁了)。
-
**3.**LruCache 自身并没有释放内存,将 LinkedHashMap 的数据移除了,如果数据还在别的地方被引用了,还是有泄漏问题,还需要手动释放内存。
-
**4.**覆写
entryRemoved
方法能知道 LruCache 数据移除是是否发生了冲突,也可以去手动释放资源。 -
5.
maxSize
和sizeOf(K key, V value)
方法的覆写息息相关,必须相同单位。( 比如 maxSize 是7MB,自定义的 sizeOf 计算每个数据大小的时候必须能算出与MB之间有联系的单位 )
ps:纯原创,参考文章如下
面试回答:
1)LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用
2)如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。maxSize是通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少
3)size在添加和移除缓存都被更新值,他通过safeSizeOf这个方法更新值。safeSizeOf默认返回1,但一般我们会根据maxSize重写这个方法,比如认为mazSize代表是KB的话,那么就以KB为单位返回该项所占的内存大小
4)除异常外,首先会判断size是否超过maxSize,如果超过了就取出最先插入的缓存,如果不为空就删掉,并把size减去该项所占的大小。这个操作将一直循环下去,直到size比maxSize小或者缓存为空