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);
  }
  • 清除缓存:
 if (lruCache != null) {
     lruCache.evictAll();
   }

源码分析

定义变量、构造器初始化
    //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);
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Android使用内存缓存图片,可以使用 LruCache 或自定义实现的内存缓存。 1. LruCache LruCacheAndroid 提供的一个可以回收不常用的 Bitmap 对象的缓存类,它的大小是通过构造函数中传入的 maxsize 来指定的。 以下是使用 LruCache 的示例代码: ``` public class ImageCache { private LruCache<String, Bitmap> mMemoryCache; public ImageCache() { int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; // 可以根据需求进行调整 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 返回 Bitmap 对象的大小,单位为 KB return bitmap.getByteCount() / 1024; } }; } public void addToMemoryCache(String key, Bitmap bitmap) { if (getFromMemoryCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getFromMemoryCache(String key) { return mMemoryCache.get(key); } } ``` 2. 自定义实现的内存缓存 自定义实现的内存缓存可以根据需求进行调整和优化,以下是一个简单的示例代码: ``` public class ImageCache { private Map<String, Bitmap> mMemoryCache; public ImageCache() { mMemoryCache = new HashMap<String, Bitmap>(); } public void addToMemoryCache(String key, Bitmap bitmap) { if (getFromMemoryCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getFromMemoryCache(String key) { return mMemoryCache.get(key); } } ``` 使用内存缓存时,需要注意内存泄漏的问题,可以在 Activity 或 Fragment 的 onDestroy() 方法中释放缓存

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_小马快跑_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值