LruCache 的使用总结

https://developer.android.google.cn/topic/performance/graphics/cache-bitmap.html
https://developer.android.google.cn/reference/android/util/LruCache.html

LRU (Least Recently Used) 就是最近最少使用算法,LruCache当然就是依据 LRU 算法实现的缓存。简单说就是,设置好缓存大小;当缓存空间不足的时候,就把最近最少使用(也就是最长时间没有使用)的缓存项清除掉;然后提供新的缓存。
LruCache 的使用其实比较简单,可以归结为以下要点:

  • LruCache 内部使用 LinkedHashMap 实现,所以 LruCache 保存的是键值对
  • LruCache 本身对缓存项是强引用
  • LruCache 的读写是线程安全的,内部加了 synchronized。也就是 put(K key, V value) 和 get(K key) 内部有 synchronized
  • key 和 value 不接受 null 。所以如果 get 到了 null ,那就说明是没有缓存
  • Override sizeOf(K key, V value) 方法
  • 根据需要Override entryRemoved(boolean evicted, K key, V oldValue, V newValue) 和 create(K key) 方法

感觉已经写完了。

好吧,还是从实际使用的角度展开一下吧。

  1. 从构造函数就能看出来,LruCache 内部使用 LinkedHashMap 实现。在《JAVA 核心技术 卷1》里面介绍 LinkedHashMap 的时候,就提到过 LinkedHashMap 可以用来做 LRU 算法。

     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);
     }
    

    并且,在构造的时候就传入了 int maxSize ,也就是缓存大小。maxSize 的具体含义,或者说单位,是和 int sizeOf(K key, V value) 的返回值对应的。
    LruCache 也提供了 resize(int maxSize) 方法重新设置缓存大小。

  2. int sizeOf(K key, V value) 用来计算每一个缓存项的大小。或者,简单点说,LruCache 是根据对每一个缓存项调用的 sizeOf 的返回值累加,判断当前缓存空间是否超出 maxSize。
    所以,实际使用 LruCache 的时候,我们可能看到这样的代码:

    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();
        }
    }
    

    当然,也可以不覆写 sizeOf 方法。这个方法默认返回1。所以,比如说我很明确这里是要缓存32个图片缩略图,不是很在乎每个缩略图具体的内存大小,就可以直接设置 maxSize 为32;而 sizeOf 默认返回1就可以。

  3. 缓存的读写,简单点说就是 put(K key, V value) 和 V get(K key)。
    不过,我觉得LruCache的代码写的不错,所以贴一下吧,学习学习。

    在put方法中可以看到,

    • 首先,key 和 value都不可以为 null。
    • 其次,如果(previous != null),说明该key之前对应了一个value,这时会调用 entryRemoved() 方法。这默认是一个空方法,我们可以根据需要 Override。
    • 还有就是,方法最后调用了 trimToSize(int maxSize) 方法,顾名思义,就是用来修剪缓存大小,使之小于 maxSize。也就是删除掉最近最少使用的缓存对象,直到 实际缓存 <= maxSize。trimToSize 方法在 while 循环内加了 synchronized (this) 来实现删除,代码很简单。
     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;
     }
    

    get 方法写的也很漂亮。

    • 如果能够从 map 中拿到缓存值,就直接 return。
    • 不然的话,就会调用 create 方法去创建一个。所以,我们可以根据需要去覆写 create 方法。这个方法默认返回 null。
    • 并且,多线程的情况下,可能存在第一步 get 为 null,调用 create 方法后,其他线程已经 put 了 key 的 value 的情况,所以就会把 被 create 方法的值覆盖的 mapValue 重新写回去。也就是有注释“// There was a conflict so undo that last put”的情况。
    // 真心觉得 Google 代码写得漂亮。
    
     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++;
         }
    
         /*
          * 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.
          */
    
         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;
         }
     }
    
  4. 从上面的分析也能看出,create 方法是在缓存 map 中没有 key 对应的 value 的情况下,构造一个默认值,并保存到缓存中。所以我们可以根据需要,选择是否覆写这个方法。
    entryRemoved 方法,是在 map 中有 value 被覆盖,或者被删除(trimToSize 方法中会删除)的时候调用,我们也可以根据需要覆写这个方法。

所以,LruCache 的使用真的很简单,在不覆写 sizeOf 方法的情况下,可以直接 new 一个,并指定 maxSize,然后 put 、get 就可以了。


LruCache 的源码中,还有一个地方我觉得写得比较好。
那就是,上面的代码中都是调用的 safeSizeOf 方法,而不是 sizeOf 方法。
而我一直在说,我们可以根据需要覆写 sizeOf 方法,这是为什么呢?

看看源码:

    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 方法,safeSizeOf 虽然最终也是调用的 sizeOf,但是增加了
异常判断。
这样的好处是, sizeOf 可以开放给最终的 API 使用者直接去覆写;而代码中,又不必到处都是对 sizeOf 的返回值的异常判断。

所以,LruCache 的整个代码,我都觉得写得很漂亮。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • 非原创,尊重作者!官方链接:http://www.jianshu.com/p/b49a111147ee 关于And...
    小小程序员jh阅读 1,834评论 0 16
  • 为了减少我们从网络获取数据(图片)的次数,我们会从网络获取到之后,缓存到我们的内存中。所以在我们网络编程中,缓存技...
    冯奕欢阅读 979评论 0 0
  • LruCache LruCache是Android3.1提供的缓存类,并且在v4包提供了该类。LruCache是一...
    Cris_Ma阅读 2,994评论 0 1
  • 1 饿汉式(静态常量)[可用]优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。缺点:在...
    待汝豪杰只是凡夫阅读 225评论 0 0
  • 有一句话叫做,没有公主命,就要有女王心。 读书时代的我,做自己的女王,心气很高,事事争强。男生们对我的评价是,孤高...
    CraZY_Emily阅读 165评论 0 0