Android LruCache 源码分析

一 什么是LruCache?

我们来看官方的解释,Lrucache是一个持有有限数量强引用对象的缓存。每当一个新的对象到来的时候,lrucache会把它移动到队列的头部。当对象到来的时候,如果这个队列已经满了,那么在队列尾部的对象就会被弹出,然后变成可以被GC回收的对象。
lrucache是线程安全的,并且Lrucache不允许value或者key是null,否则会返回the key was not in the cache。

二 开始进入正题

1 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包含了一个LinkHashMap,为什么不用hashMap?因为在开头我们说到,Lrucache是一个线程安全的cache,二hashMap是线程不安全的,所以只能采用LinkHashMap。
2 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++;
        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;
}

lrucache的put流程就是简单的把一个value移动到队列的开头,value和key是一一匹配的。我们接下来分析:
1 判断key或者value是否为空,为空的话,那么就跑出异常
2 定义一个value,赋值为previous,这个值表示当前key,是不是有值在cache中了
3 通过map.put(key,value),如果有值,那么previous返回的是原先的值
4 如果previous不为空,那么就给出一个entryRemoved的回调
5 调用trimToSize(maxisize)方法,按照情况判断是否需要弹出元素
3 trimToSize(maxisize)方法

 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();
            map.remove(key);
            size -= safeSizeOf(key, value);
            evictionCount++;
        }

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

这方法的执行步奏是,如果当前的size比maxsize还要小,那么就继续填加元素。否则,就找出map的最后一个元素,如果他不为空的时候,就把元素弹出,并且把当前的map的size重新复制

private int safeSizeOf(K key, V value) {
    int result = sizeOf(key, value);
    if (result < 0) {
        throw new IllegalStateException("Negative size: " + key + "=" + value);
    }
    return result;
}

protected int sizeOf(K key, V value) {
    return 1;
}

在默认情况下,sizeof()方法默认返回的是1,表示当前size是元素的值,maxsize是最大的元素值
4 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++;
            return mapValue;
        }
        missCount++;
    }

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

这个方法的大致作用是,通过get方法会根据key返回对应的value,如果这个value被返回,那么这个元素将会被移动到队列的头部。当value没有被缓存或者没有被创建的时候,就会返回null。
好了第一步:

 mapValue = map.get(key);
        if (mapValue != null) {
            hitCount++;
            return mapValue;
        }

如果通过map可以找到对应的值,那么就直接返回map中的value;否则,调用create(key)方法。create(key)方法可能会花费很多的时间,同时,在create方法执行的时候,map可能已经发生了变化。所以当一个key已经被放进map中的时候,我们以map中的value为准,释放掉create(key)方法中创建的值。

那么什么时候会去调用oncreate()方法呢?其实oncreate()方法是需要我们去重写的。如果没有重写oncreate方法,那么LruCache默认返回的是0,整个流程就返回了。
好了,我们现在假设我们重写的onCreate()方法。流程往下走。我们将createVaule put进map中,发现如果有冲突了,那么说明我们这个key在我们创建的时候,别的线程已经赋值了,那么我们就回撤这个行为,将mapvalue重新put进去map中,也就是上面所说的以map中的value为准。如果put进去map成功了,那么我们就需要走上面的put内容,重新走trimToSize()方法。
4 lrucache的remove()流程
其实这个流程比较简单就是简单的map的remove,话不多说上源码:

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

根据keyremove对应的value,同时把size-safeSizeOf(key,previous)的值。如果为空,那么返回null。

三 备注

至此整个LruCache算法介绍完毕。LruCache算法更多的用于设计图片加载框架,这个我们以后再谈。。。

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

推荐阅读更多精彩内容