博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android LruCache 二级缓存
阅读量:6155 次
发布时间:2019-06-21

本文共 11524 字,大约阅读时间需要 38 分钟。

hot3.png

简介

    LRU全称Least Recently Used,顾名思义就是缓存最近使用频率高的缓存,具体数量根据指定的存储空间大小决定。内部存在一个LinkedHashMap和maxSize,把最近使用的对象用强引用存储在 LinkedHashMap中,给出来put和get方法,每次put图片时计算缓存中所有图片总大小,跟maxSize进行比较,大于maxSize,就将最久添加的图片移除;反之小于maxSize就添加进来。 

之前,我们会使用内存缓存技术实现,也就是软引用或弱引用,在Android 2.3(APILevel 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。

       LRU是缓存使用频率算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种:LrhCache和DisLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法。

 

LruCache的使用

LruCache的使用非常简单,我们就已图片缓存为例。

int maxMemory = (int) (Runtime.getRuntime().totalMemory()/1024);        int cacheSize = maxMemory/8;        mMemoryCache = new LruCache
(cacheSize){ @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes()*value.getHeight()/1024; } };

①设置LruCache缓存的大小,一般为当前进程可用容量的1/8。

②重写sizeOf方法,计算出要缓存的每张图片的大小。

注意:缓存的总容量和每个缓存对象的大小所用单位要一致。

 

源码分析

package android.support.v4.util;import java.util.LinkedHashMap;import java.util.Map;/** * Static library version of {@link android.util.LruCache}. Used to write apps * that run on API levels prior to 12. When running on API level 12 or above, * this implementation is still used; it does not try to switch to the * framework's implementation. See the framework SDK documentation for a class * overview. */public class LruCache
{ private final LinkedHashMap
map; /** Size of this cache in units. Not necessarily the number of elements. */ private int size; //当前cache的大小 private int maxSize; //cache最大大小 private int putCount; //put的次数 private int createCount; //create的次数 private int evictionCount; //回收的次数 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; //将LinkedHashMap的accessOrder设置为true来实现LRU this.map = new LinkedHashMap
(0, 0.75f, true); } /** * 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. * 通过key获取相应的item,或者创建返回相应的item。相应的item会移动到队列的尾部, * 如果item的value没有被cache或者不能被创建,则返回null。 */ 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) { //mapValue不为空表示命中,hitCount+1并返回mapValue对象 hitCount++; return mapValue; } missCount++; //未命中 } /* * Attempt to create a value. This may take a long time, and the map * may be different when create() returns. If a conflicting value was * added to the map while create() was working, we leave that value in * the map and release the created value. * 如果未命中,则试图创建一个对象,这里create方法返回null,并没有实现创建对象的方法 * 如果需要事项创建对象的方法可以重写create方法。因为图片缓存时内存缓存没有命中会去 * 文件缓存中去取或者从网络下载,所以并不需要创建。 */ V createdValue = create(key); if (createdValue == null) { return null; } //假如创建了新的对象,则继续往下执行 synchronized (this) { createCount++; //将createdValue加入到map中,并且将原来键为key的对象保存到mapValue mapValue = map.put(key, createdValue); if (mapValue != null) { // There was a conflict so undo that last put //如果mapValue不为空,则撤销上一步的put操作。 map.put(key, mapValue); } else { //加入新创建的对象之后需要重新计算size大小 size += safeSizeOf(key, createdValue); } } if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { //每次新加入对象都需要调用trimToSize方法看是否需要回收 trimToSize(maxSize); return createdValue; } } /** * 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++; size += safeSizeOf(key, value); //size加上预put对象的大小 previous = map.put(key, value); if (previous != null) { //如果之前存在键为key的对象,则size应该减去原来对象的大小 size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, value); } //每次新加入对象都需要调用trimToSize方法看是否需要回收 trimToSize(maxSize); return previous; } /** * @param maxSize the maximum size of the cache before returning. May be -1 * to evict even 0-sized elements. * 此方法根据maxSize来调整内存cache的大小,如果maxSize传入-1,则清空缓存中的所有对象 */ private 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或者map没有任何对象,则结束循环 if (size <= maxSize || map.isEmpty()) { break; } //移除链表头部的元素,并进入下一次循环 Map.Entry
toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; //回收次数+1 } entryRemoved(true, key, value, null); } } /** * Removes the entry for {@code key} if it exists. * * @return the previous value mapped by {@code key}. * 从内存缓存中根据key值移除某个对象并返回该对象 */ public final V remove(K key) { if (key == null) { throw new NullPointerException("key == null"); } V previous; synchronized (this) { previous = map.remove(key); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, null); } return previous; } /** * Called for entries that have been evicted or removed. This method is * invoked when a value is evicted to make space, removed by a call to * {@link #remove}, or replaced by a call to {@link #put}. The default * implementation does nothing. * *

The method is called without synchronization: other threads may * access the cache while this method is executing. * * @param evicted true if the entry is being removed to make space, false * if the removal was caused by a {@link #put} or {@link #remove}. * @param newValue the new value for {@code key}, if it exists. If non-null, * this removal was caused by a {@link #put}. Otherwise it was caused by * an eviction or a {@link #remove}. */ protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} /** * Called after a cache miss to compute a value for the corresponding key. * Returns the computed value or null if no value can be computed. The * default implementation returns null. * *

The method is called without synchronization: other threads may * access the cache while this method is executing. * *

If a value for {@code key} exists in the cache when this method * returns, the created value will be released with {@link #entryRemoved} * and discarded. This can occur when multiple threads request the same key * at the same time (causing multiple values to be created), or when one * thread calls {@link #put} while another is creating a value for the same * key. */ protected V create(K key) { return null; } private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } return result; } /** * 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. * *

An entry's size must not change while it is in the cache. * 用来计算单个对象的大小,这里默认返回1,一般需要重写该方法来计算对象的大小 * xUtils中创建LruMemoryCache时就重写了sizeOf方法来计算bitmap的大小 * mMemoryCache = new LruMemoryCache

(globalConfig.getMemoryCacheSize()) { * @Override * protected int sizeOf(MemoryCacheKey key, Bitmap bitmap) { * if (bitmap == null) return 0; * return bitmap.getRowBytes() * bitmap.getHeight(); * } * }; * */ protected int sizeOf(K key, V value) { return 1; } /** * Clear the cache, calling {@link #entryRemoved} on each removed entry. * 清空内存缓存 */ public final void evictAll() { trimToSize(-1); // -1 will evict 0-sized elements } /** * For caches that do not override {@link #sizeOf}, this returns the number * of entries in the cache. For all other caches, this returns the sum of * the sizes of the entries in this cache. */ public synchronized final int size() { return size; } /** * For caches that do not override {@link #sizeOf}, this returns the maximum * number of entries in the cache. For all other caches, this returns the * maximum sum of the sizes of the entries in this cache. */ public synchronized final int maxSize() { return maxSize; } /** * Returns the number of times {@link #get} returned a value. */ public synchronized final int hitCount() { return hitCount; } /** * Returns the number of times {@link #get} returned null or required a new * value to be created. */ public synchronized final int missCount() { return missCount; } /** * Returns the number of times {@link #create(Object)} returned a value. */ public synchronized final int createCount() { return createCount; } /** * Returns the number of times {@link #put} was called. */ public synchronized final int putCount() { return putCount; } /** * Returns the number of values that have been evicted. */ public synchronized final int evictionCount() { return evictionCount; } /** * Returns a copy of the current contents of the cache, ordered from least * recently accessed to most recently accessed. */ public synchronized final Map
snapshot() { return new LinkedHashMap
(map); } @Override public synchronized final String toString() { int accesses = hitCount + missCount; int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", maxSize, hitCount, missCount, hitPercent); }}

 

 

 

转载于:https://my.oschina.net/ososchina/blog/1604065

你可能感兴趣的文章
简洁优雅地实现夜间模式
查看>>
react学习总结
查看>>
在soapui上踩过的坑
查看>>
MySQL的字符集和字符编码笔记
查看>>
ntpd同步时间
查看>>
must implement java.io.Serializable hessian
查看>>
Microsoft Licenses Flash Lite for Windows Mobile Users
查看>>
HDOJ 2020 绝对值排序
查看>>
HDOJ/HDU 2560 Buildings(嗯~水题)
查看>>
Maven编译时跳过Test
查看>>
Spring Boot 整合Spring Security 和Swagger2 遇到的问题小结
查看>>
[20170628]12C ORA-54032.txt
查看>>
linux运维人员的成功面试总结案例分享
查看>>
Windows DHCP Server基于MAC地址过滤客户端请求实现IP地址的分配
查看>>
命令查询每个文件文件数
查看>>
《跟阿铭学Linux》第8章 文档的压缩与打包:课后习题与答案
查看>>
RAC表决磁盘管理和维护
查看>>
Apache通过mod_php5支持PHP
查看>>
发布一个TCP 吞吐性能测试小工具
查看>>
java学习:jdbc连接示例
查看>>