Android LruCache 缓存源码解析

1.背景

  在实际开发中,缓存机制使用最频繁的便是图片缓存!目前大部分的App都是图文结合,从web服务器获取文字和图片,文字显示很快,图片基本上是先下载到手机本地,然后再显示,如果图片很多、很大,每次加载同一张图片,都去网络下载,那么App渲染的速度是比较慢的,这样的体验很差!所以,类似这样的场景,便要使用缓存机制!

  目前缓存机制使用大致流程是,当App需要加载某一张图片时,先去手机内存中去找该图片,如果有,那么直接显示,如果无,则去手机sd卡或者手机外部存储中找该图片,如果有,那么直接显示,如果无,那么此时才去网络下载该图片。这种机制常称为三级缓存策略。

2.简介

  LruCache这个类在android.util包下,是API level 12引入的,对于API level 12之前的系统可以使用support library中的LruCache。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

3.LruCache的使用

//设置缓存的大小,基本上设置为手机内存的1/8
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
//LruCache里面的键值对分别是URL和对应的图片
LruCache<String, Bitmap> mCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap value) {
        //在每次存入缓存的时候调用,计算出要缓存的每张图片的大小
        return value.getByteCount();
    }
};

4.LruCache实现原理

 LruCache内部的缓存实际是由LinkedHashMap来维护的,下面是LinkedHashMap的构造函数

public LinkedHashMap(int initialCapacity,    //初始容量
                     float loadFactor,       //加载因子
                     boolean accessOrder) {  //排序方式,true则按访问顺序,为false,则按插入顺序
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

 下面是Lrucache的构造方法

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);  //LruCache默认为按访问排序
}

 LruCache中的put()方法

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++;  //put方法调用次数加1
        size += safeSizeOf(key, value);  //safeSizeOf()方法最终会调用创建LruCache时
                                         //重写的SizeOf()方法并返回该方法的值
        previous = map.put(key, value);  //执行该方法时内部会判断该值是否已存在
        if (previous != null) {          //该值若存在会赋值给previous,否则为空
            size -= safeSizeOf(key, previous);
        }
    }

    if (previous != null) {
        entryRemoved(false, key, previous, value); //该方法需要在创建LruCache时重写,若不重写,则不作任何操作。
    }

    trimToSize(maxSize);  ////调整缓存大小(关键方法)
    return previous;
}

  可以看到put()方法并没有什么难点,重要的就是在添加过缓存对象后,调用 trimToSize()方法,来判断缓存是否已满,如果满了就要删除近期最少使用的算法。

trimToSize()方法

public void trimToSize(int maxSize) {
    while (true) {
        K key;
        V value;
        //如果map为空并且缓存size不等于0或者缓存size小于0,抛出异常
        synchronized (this) {
            if (size < 0 || (map.isEmpty() && size != 0)) {
                throw new IllegalStateException(getClass().getName()
                        + ".sizeOf() is reporting inconsistent results!");
            }
            //如果缓存大小size小于最大缓存,或者map为空,不需要再删除缓存对象,跳出循环
            if (size <= maxSize) {
                break;
            }
            //迭代器获取第一个对象,即队尾的元素,近期最少访问的元素
            Map.Entry<K, V> toEvict = map.eldest();
            if (toEvict == null) {
                break;
            }
            //删除该对象,并更新缓存大小
            key = toEvict.getKey();
            value = toEvict.getValue();
            map.remove(key);
            size -= safeSizeOf(key, value);
            evictionCount++;
        }

        entryRemoved(true, key, value, null);
    }
}

LruCache中的get()方法

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++;            //get获取缓存命中时次数加1
            return mapValue;       //获取到值跳出方法返回取到的值
        }
        missCount++;               //get获取缓存未命中时次数加1
    }

    V createdValue = create(key);  //create()方法需要重写,不重写则返回null
    if (createdValue == null) {
        return null;
    }

    synchronized (this) {
        createCount++;             //create()方法成功调用次数加1
        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;
    }
}

 LinkedHashMap中的get()方法

public V get(Object key) {
    LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
    if (e == null)
        return null;
    e.recordAccess(this);  //实现排序的关键方法
    return e.value;
}

$emsp;recordAccess()方法

void recordAccess(HashMap<K,V> m) {
    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
    //判断是否是访问排序
    if (lm.accessOrder) {
        lm.modCount++;
        remove();                   //删除此元素
        addBefore(lm.header);       //将此元素移动到队列的头部
    }
}

以上便是LruCache实现的原理,理解了LinkedHashMap的数据结构就能理解整个原理。如果不懂,可以先看看LinkedHashMap的具体实现。

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