LruCache
一般来说,缓存的策略主要包含缓存的添加、获取和删除
。但由于缓存的大小都是有上限的。缓存慢了之后,要想继续添加 ,就需要删除一些旧的缓存以提供空间。
所以使用到了LruCache
缓存算法,即最近最少使用算法,当缓存满时,会优先淘汰掉 最近最少使用的缓存对象。
LruCache的核心原理就是利用了LinkedHashMap。
LruCache的使用
1 2 3 4 5 6 7 8 9 10
| int maxMemory = (int)(Runtime.getRuntime().totalMemory()/1024);
int cacheSize = maxMemory/8; LruCache memoryCache = new LruCache<String,Bitmap>(cacheSize){ @Override protected int sizeOf(@NonNull String key, @NonNull Bitmap value) { return value.getRowBytes() * value.getHeight() / 1024; } };
|
LruCache的实现原理
LruCache内部需要维护好一个缓存对象列表,其中对象的排列方式应该按照访问顺序排列的,即一直没访问的对象,要放在队尾,最近访问的对象就会放在对头,最晚被淘汰。
查看源码中发现内部是利用了LinkedHashMap
去缓存对象的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private final LinkedHashMap<K, V> map; public LruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } else { this.maxSize = maxSize; this.map = new LinkedHashMap(0, 0.75F, true); } }
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
|
在LruCache
构造方法中,设置了maxSize
以及创建一个LinkedHashMap
对象用来存储对象。
LruCache
中需要移除最近最少使用的对象,即为优先删除访问最早对象,所以应该按照访问顺序排列,为true。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public final V get(K key) { if (key == null) { throw new NullPointerException("key == null"); }
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) { map.put(key, mapValue); } else { size += safeSizeOf(key, createdValue); } }
if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { trimToSize(maxSize); return createdValue; } }
|
LruCache的get()
实际调用的就是LinkedHashMap
对应的get(key)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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; }
transient LinkedHashMapEntry<K,V> head;
transient LinkedHashMapEntry<K,V> tail;
void afterNodeAccess(Node<K,V> e) { LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<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; } }
|
先调用getNode()
获取key对应节点,如果不存在则返回null。若存在并且需要按照访问顺序排列,就把找到的节点移到双端链表的尾部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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++; 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; }
|
在调用put
过后,需要调用一次trimToSize()
调整缓存对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public void trimToSize(int maxSize) { while(true) { Object key; Object value; synchronized(this) { if (this.size < 0 || this.map.isEmpty() && this.size != 0) { throw new IllegalStateException(this.getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } if (this.size <= maxSize || this.map.isEmpty()) { return; } Entry<K, V> toEvict = (Entry)this.map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); this.map.remove(key); this.size -= this.safeSizeOf(key, value); ++this.evictionCount; }
this.entryRemoved(true, key, value, (Object)null); } }
|
原理总结:
内部是利用了LinkedHashMap
来实现一个最近最少使用算法
,在每次调用put
和get
时,都会算作一次对LinkedHashMap
的访问,当设置accessOrder
为true
时,就会按照访问顺序排列,就会把每次访问的元素放在尾部,当缓存值达到阈值maxSzie
后,就会去删除LinkedHashMap
的首部元素,来降低内存占用。
LinkedHashMap
在HashMap
基础上使用了一个双端链表维持有序的节点。
自定义LRUCache