关于Android内存缓存LruCache的使用及其源码解析

 更新时间:2023年05月31日 08:30:06   作者:_小马快跑_  
LruCache作为内存缓存,使用强引用方式缓存有限个数据,当缓存的某个数据被访问时,它就会被移动到队列的头部,本文详细介绍了关于Android内存缓存LruCache的使用及其源码解析,需要的朋友可以参考下

整体介绍

LruCache 作为内存缓存,使用强引用方式缓存有限个数据,当缓存的某个数据被访问时,它就会被移动到队列的头部,当一个新数据要添加到LruCache而此时缓存大小要满时,队尾的数据就有可能会被垃圾回收器(GC)回收掉,LruCache使用的LRU(Least Recently Used)算法,即:把最近最少使用的数据从队列中移除,把内存分配给最新进入的数据。

  • 如果LruCache缓存的某条数据明确地需要被释放,可以覆写entryRemoved(boolean evicted, K key, V oldValue, V newValue)
  • 如果LruCache缓存的某条数据通过key没有找到,可以覆写 create(K key),这简化了调用代码,即使错过一个缓存数据,也不会返回 null,而会返回通过create(K key) 创造的数据。
  • 如果想限制每条数据的缓存大小,可以覆写 sizeOf(K key, V value) ,如下面设置bitmap最大缓存大小是4MB:
 int cacheSize = 4 * 1024 * 1024; // 4MiB
 LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
     protected int sizeOf(String key, Bitmap value) {
         return value.getByteCount();
     }
 }

LruCache 这个类是线程安全的。自动地执行多个缓存操作通过synchronized 同步缓存:

 synchronized (cache) {
   if (cache.get(key) == null) {
       cache.put(key, value);
   }
 }

LruCache不允许 null 作为一个 key 或 valueget(K)、put(K,V),remove(K) 中如果key 或 value 为 null,都会抛出异常:throw new NullPointerException("key == null || value == null")

常用API

方法备注
void resize(int maxSize)更新存储大小
V put(K key, V value)存数据,返回之前key对应的value,如果没有,返回null
V get(K key)取出key对应的缓存数据
V remove(K key)移除key对应的value
void evictAll()清空缓存数据
Map<K, V> snapshot()复制一份缓存并返回,顺序从最近最少访问到最多访问排序

使用示例

  • 初始化:
 private Bitmap bitmap;
 private String STRING_KEY = "data_string";
 private LruCache<String, Bitmap> lruCache;
 private static final int CACHE_SIZE = 10 * 1024 * 1024;//10M
 lruCache = new LruCache<String, Bitmap>(CACHE_SIZE) {
    @Override
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
       super.entryRemoved(evicted, key, oldValue, newValue);
    }
    @Override
    protected int sizeOf(String key, Bitmap value) {
        //这里返回的大小用单位kb来表示的
        return value.getByteCount() / 1024;
    }
};

最大缓存设置为10M,key是String类型,value设置的是Bitmap

  • 存数据:
  if (lruCache!=null){
     lruCache.put(STRING_KEY, bitmap);
   }
  • 取数据:
 if (lruCache != null) {
     bitmap = lruCache.get(STRING_KEY);
  }
  • 清除缓存:

源码分析

定义变量、构造器初始化

    //LruCache.java
    private final LinkedHashMap<K, V> map;//使用LinkedHashMap来存储操作数据
    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;//缓存数据大小,不一定等于元素的个数
    private int maxSize;//最大缓存大小
    private int putCount;// 成功添加数据的次数
    private int createCount;//手动创建缓存数据的次数
    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;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

构造函数中初始化了 LinkedHashMap,参数maxSize指定了缓存的最大大小。

修改缓存大小

/**
 * Sets the size of the cache.
 *
 * @param maxSize The new maximum size.
 */
public void resize(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    synchronized (this) {
        this.maxSize = maxSize;
    }
    trimToSize(maxSize);
}

resize(int maxSize) 用来更新大小,先是加同步锁更新maxSize的值,接着调用了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) {
            if (size < 0 || (map.isEmpty() && size != 0)) {
                throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
            }
            //如果已经小于最大缓存,则无需接着往下执行了
            if (size <= maxSize) {
                break;
            }
            //拿到最近最少使用的那条数据
            Map.Entry<K, V> toEvict = map.eldest();
            if (toEvict == null) {
                break;
            }
            key = toEvict.getKey();
            value = toEvict.getValue();
            //从LinkedHashMap移除这条最少使用的数据
            map.remove(key);
            //缓存大小size减去移除数据的大小,如果没有覆写sizeOf,则减去的值是1
            size -= safeSizeOf(key, value);
            evictionCount++;
        }
        entryRemoved(true, key, value, 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.
 *
 * <p>An entry's size must not change while it is in the cache.
 */
 protected int sizeOf(K key, V value) {
     return 1;
 }
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

trimToSize() 循环移除最近最少使用的数据直到剩余缓存数据的大小等于小于最大缓存大小。 注:我们看到sizeOf()entryRemoved()都是protected来修饰的,即可以被覆写,如果sizeOf()没有被覆写,那么变量size 代表的是缓存数据的数量,maxSize代表的是最大数量,如果覆写sizeOf(),如:

 @Override
  protected int sizeOf(String key, BitmapDrawable value) {
      return value.getBitmap().getByteCount() / 1024;
  }

此时size 代表的是缓存数据的大小maxSize代表的是最大缓存大小

存数据

/**
 * 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) {
    //key或value为空直接抛异常
    if (key == null || value == null) {
        throw new NullPointerException("key == null || value == null");
    }
    V previous;
    synchronized (this) {
        //添加数据次数+1
        putCount++;
         //缓存数据的大小增加
        size += safeSizeOf(key, value);
         //添加缓存数据,添加之前如果key值对应的value不为空,则newValue会覆盖oldValue,并返回oldValue;
         //如果key值对应的value为空,则返回Null
        previous = map.put(key, value);
        if (previous != null) {
           //之前的oldValue不为空,则在缓存size中减去oldValue
            size -= safeSizeOf(key, previous);
        }
    }
    if (previous != null) {
        entryRemoved(false, key, previous, value);
    }
    //重新检查缓存大小
    trimToSize(maxSize);
    return previous;
}

调用 LinkedHashMapput(key, value) 添加缓存数据后,在添加之前如果 key 值对应的value 不为空,则 newValue 会覆盖 oldValue ,并返回 oldValue;如果 key 值对应的 value 为空,则返回 null,接着根据返回值来重新设置缓存 size 和最大缓存 maxSize 的大小。

取数据

 /**
  * 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) {
         throw new NullPointerException("key == null");
     }
     V mapValue;
     synchronized (this) {
         //通过key取数据
         mapValue = map.get(key);
         if (mapValue != null) {
             //如果取到了数据,命中次数+1
             hitCount++;
             return mapValue;
         }
         //没有取到数据,未命中此时+1
         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(),默认create()方法返回的null
     V createdValue = create(key);    
     if (createdValue == null) {
         return null;
     }
     //如果覆写了create(),即根据key值手动创造了value,则继续往下执行
     synchronized (this) {
          //创造数据次数+1
         createCount++;
         //尝试将数据添加到缓存中
         mapValue = map.put(key, createdValue);
         if (mapValue != null) {
             // There was a conflict so undo that last put
             //mapValue不为空,说明之前的key对应的是有数据的,那么就跟我们手动创建的数据冲突了,
             //所以执行撤消操作,重新把mapValue添加到缓存中,用mapValue去覆盖createdValue
             map.put(key, mapValue);
         } else {
             //如果mapValue为空,说明之前的key值对应的value确实为空,我们手动添加createdValue后,
             //需要重新计算缓存size的大小
             size += safeSizeOf(key, createdValue);
         }
     }
     if (mapValue != null) {
         entryRemoved(false, key, createdValue, mapValue);
         return mapValue;
     } else {
         trimToSize(maxSize);
         return createdValue;
     }
 }
 protected V create(K key) {
      return null;
  }

取数据的流程大致是这样:

  • 首先通过 map.get(key) 来尝试取出 value,如果value存在,则直接返回value
  • 如果value不存在,则执行下面的create(key),如果create()没有被覆写,则直接返回null
  • 如果 create() 被覆写了,即通过 key 值创建了一个createdValue,那么尝试通过mapValue = map.put(key, createdValue)createdValue 添加到缓存中去,mapValueput()方法的返回值,mapValue代表的是key之前对应的值,如果mapValue不为空,说明之前的key对应的是有数据的,那么就跟我们手动创建的数据冲突了,所以执行撤消操作,重新把mapValue添加到缓存中,用mapValue去覆盖createdValue,最后再重新计算缓存大小。

移除数据

 /**
  * Removes the entry for {@code key} if it exists.
  *
  * @return the previous value mapped by {@code 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;
 }

调用 map.remove(key) 通过 key 值删除缓存中 key 对应的value,然后重新计算缓存大小,并返回删除的value

其他一些方法:

      //清空缓存
     public final void evictAll() {
          trimToSize(-1); // -1 will evict 0-sized elements
      }
     public synchronized final int size() {
         return size;
     }
     public synchronized final int maxSize() {
          return maxSize;
     }
     public synchronized final int hitCount() {
         return hitCount;
     }
     public synchronized final int missCount() {
         return missCount;
     }
    public synchronized final int createCount() {
         return createCount;
     }
     public synchronized final int putCount() {
         return putCount;
     }
     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<K, V> snapshot() {
         return new LinkedHashMap<K, V>(map);
     }

以上就是关于Android内存缓存LruCache的使用及其源码解析的详细内容,更多关于Android LruCache的使用的资料请关注脚本之家其它相关文章!

相关文章

  • Android 自定义阴影效果详解及实例

    Android 自定义阴影效果详解及实例

    这篇文章主要介绍了Android 自定义阴影效果详解及实例的相关资料,需要的朋友可以参考下
    2017-02-02
  • Android中监听软键盘显示状态实现代码

    Android中监听软键盘显示状态实现代码

    这篇文章主要介绍了Android中监听软键盘显示状态实现代码,本文直接给出核心实现代码,需要的朋友可以参考下
    2015-04-04
  • 关于Android Activity之间传递数据的6种方式

    关于Android Activity之间传递数据的6种方式

    这篇文章主要介绍了关于Android Activity之间传递数据的6种方式,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-03-03
  • Kotlin中空判断与问号和感叹号标识符使用方法

    Kotlin中空判断与问号和感叹号标识符使用方法

    最近使用kotlin重构项目,遇到了一个小问题,在Java中,可能会遇到判断某个对象是否为空,为空执行一段逻辑,不为空执行另外一段逻辑,下面这篇文章主要给大家介绍了关于Kotlin中空判断与问号和感叹号标识符处理操作的相关资料,需要的朋友可以参考下
    2022-12-12
  • Ubuntu中为Android系统上编写Linux内核驱动程序实现方法

    Ubuntu中为Android系统上编写Linux内核驱动程序实现方法

    本文主要介绍在Ubuntu 上为Android系统编写Linux内核驱动程序, 这里对编写驱动程序做了详细的说明,对研究Android源码和HAL都有巨大的帮助,有需要的小伙伴可以参考下
    2016-08-08
  • Android自定义PhotoView使用教程

    Android自定义PhotoView使用教程

    自定义 PhotoView 继承(extends)自 View。并在最中间显示后面操作的图片。绘制图片可以重写 onDraw()方法,并在里面通过Canvas.drawBitmap()来要绘制图片
    2023-04-04
  • Android仿微信联系人按字母排序

    Android仿微信联系人按字母排序

    但凡涉及到联系人界面,几乎都是按照字母排序的,那么联系人按字母排序是怎么实现的呢,下面小编就给大家详解Android仿微信联系人按字母排序,需要的朋友可以参考下
    2015-08-08
  • Android 进度条自动前进效果的实现代码

    Android 进度条自动前进效果的实现代码

    这篇文章主要介绍了Android 进度条自动前进效果,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • 自定义AlertDialog去除黑色背景的解决方法

    自定义AlertDialog去除黑色背景的解决方法

    今天小编就为大家分享一篇自定义AlertDialog去除黑色背景的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-07-07
  • Android超详细讲解组件AdapterView的使用

    Android超详细讲解组件AdapterView的使用

    AdapterView组件是一组重要的组件,AdapterView本身是一个抽象基类,它派生的子类在用法上十分相似,从AdapterView派生出的三个子类:AdsListView、AdsSpinner、AdapterViewAnimator,这3个子类依然是抽象的,实际运用时需要它们的子类
    2022-03-03

最新评论