资讯专栏INFORMATION COLUMN

Android LruCache源码分析

Kyxy / 1674人阅读

摘要:和变量分别表示当前缓存数据的大小以及缓存最大可使用的大小。的构造方法接受一个变量,用于指定缓存的最大使用量。第一个是初始容量,第二个是加载因子,这两个都是中的概念源码分析。

简介

Android 中常常会用通过网络请求数据,为了节省流量、电量以及时间等等,一般会把得到的数据进行缓存。缓存分为内存缓存和文件缓存。Android 自带的内存缓存是 LRU 机制,也即是最近最少使用算法,对应的类是 LruCache。要说它的原理,一句话概括就是使用了 LinkedHashMap。本文具体分析 LruCache 源码的实现,其实还是比较简单的。

变量及构造方法

LruCache 的内部变量及构造方法如下:

private final LinkedHashMap map;

private int size;
private int maxSize;

private int putCount;
private int createCount;
private int evictionCount;
private int hitCount;
private int missCount;

public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    this.map = new LinkedHashMap(0, 0.75f, true);
}

LruCache 中,有一个 LinkedHashMap 变量,它就是实际存储缓存数据的。LinkedHashMap 继承自 HashMap,但是增加了记住元素插入或者访问顺序的功能,这个是通过内部一个双向的循环链表实现的。

sizemaxSize 变量分别表示当前缓存数据的大小以及缓存最大可使用的大小。下面的几个以 Count 结尾的变量是记录相应操作的命中次数。

LruCache 的构造方法接受一个 int 变量,用于指定缓存的最大使用量。构造方法中创建了 LinkedHashMap, 它有三个参数。第一个是初始容量,第二个是加载因子,这两个都是 HashMap 中的概念 (Java HashMap源码分析)。第三个参数是一个布尔值,true 表示 LinkedListMap 按照元素的最近访问排序,false 则表示按照元素的插入次序排序,LruCache 实现的是最近最少访问,所以这里指定为 true

put 和 get

LruCache 创建后最常用的两个操作就是 putget 了。先看 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;
}

protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

如果 key 或者 value 为空会抛出异常,否则在同步块中进行添加操作。首先是 putCount 加一,然后调用 safeSizeOf 方法增加 size,接着把数据放到 map 中,如果这个 key 已经存放了数据,那么应该减去这条数据的大小,因为它已经被覆盖调了。同步块结束后,如果确实覆盖了数据,会调用 entryRemoved,这个方法默认是空,什么也没做,我们自己创建 LruCache 时可以选择重写。最后还需要调用 trimToSize,这个方法用来防止数据超出 maxSize

上面在计算 size 大小时调用了 safeSizeOf 方法,看名字就觉得不一般,继续看它的代码:

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 返回数据的大小,如果小于 0 抛出异常,否则就返回。sizeOf 这个方法是我们熟悉的,一般使用 LruCache 都会重写这个方法返回每条数据的实际大小。为什么要重写呢? 因为这个方法默认的实现是返回 1。这样的话,size 相当于记录的是缓存数据的条数,而这可能并不是我们想要的。

下面再看看 trimToSize 的实现:

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

内部是一个无限循环,删除 map 里面最久未使用的,然后更新 size,如果 size 小于 maxSize 就跳出循环。

put相关的代码就是这样,现在看看 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++;
    }

    //找不到就创建一个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;
    }
}

protected V create(K key) {
    return null;
}

key 依然不能为空,然后就是从 map 中取数据,递增hitCount,最后直接返回数据。这是成功找到缓存的情况,如果找不到还会执行下面的代码。下面的逻辑是调用 create 创建 value。create需要我们自己重写,默认返回 null,所以默认情况下找不到缓存就返回 null。 如果重写了 create 那么接着会把新建的数据加入 map,并且增加 size,执行 trimToSize 等操作。

其它操作

除了 getput,其它还有一些操作。比如 evictAll 用于清除所有缓存,size() 返回 size 大小,一系列的以 Count 结尾的方法用于返回 hitCount 等计数值的大小。这些代码都比较简单,没什么好说的。

总结

LruCache 实现了数据的内存缓存,可以看出整体思路并不是很复杂,关键在于使用了 LinkedHashMap

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/12732.html

相关文章

  • LruCache的缓存策略

    摘要:采用算法的缓存有两种和分别用于实现内存缓存和硬盘缓存,其核心思想都是缓存算法。一、Android中的缓存策略 一般来说,缓存策略主要包含缓存的添加、获取和删除这三类操作。如何添加和获取缓存这个比较好理解,那么为什么还要删除缓存呢?这是因为不管是内存缓存还是硬盘缓存,它们的缓存大小都是有限的。当缓存满了之后,再想其添加缓存,这个时候就需要删除一些旧的缓存并添加新的缓存。 因此LRU(Least...

    Awbeci 评论0 收藏0
  • Bitmap之内存缓存和磁盘缓存详解

    摘要:原文首发于微信公众号,欢迎关注交流中缓存的使用比较普遍,使用相应的缓存策略可以减少流量的消耗,也可以在一定程度上提高应用的性能,如加载网络图片的情况,不应该每次都从网络上加载图片,应该将其缓存到内存和磁盘中,下次直接从内存或磁盘中获取,缓 原文首发于微信公众号:jzman-blog,欢迎关注交流! Android 中缓存的使用比较普遍,使用相应的缓存策略可以减少流量的消耗,也可以在一定...

    masturbator 评论0 收藏0
  • Android面试(附答案)

    摘要:几个月前在西安买了房,所以最近总结了一些还算全面的面试题。面试题机制垃圾回收需要完成两件事找到垃圾,回收垃圾。需要校验字节信息是否符合规范,避免恶意信息和不规范数据危害运行安全。具有相同哈希值的键值对会组成链表。 写在开头 由于杭州的房价实在太高,所以我可耻的跑路到了西安。几个月前在西安买了房,所以最近总结了一些还算全面的Android面试题。还好成功的通过了西安努比亚的面试,虽然不是...

    xiaowugui666 评论0 收藏0
  • Android面试整理(附答案)

    摘要:和体现了对不同异常情况的分类。是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应的处理。是指在正常情况下,不大可能出现的情况,绝大部分都会使程序处于非正常不可恢复的状态。常见的非对称加密包括等。 面试,无非都是问上面这些问题(挺多的 - -!),聘请中高级的安卓开发会往深的去问,并且会问一延伸二。以下我先提出几点重点,是面试官基本必问的问题,请一定要去了解! 基础知识...

    Cruise_Chan 评论0 收藏0
  • Android面试整理(附答案)

    摘要:和体现了对不同异常情况的分类。是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应的处理。是指在正常情况下,不大可能出现的情况,绝大部分都会使程序处于非正常不可恢复的状态。常见的非对称加密包括等。 面试,无非都是问上面这些问题(挺多的 - -!),聘请中高级的安卓开发会往深的去问,并且会问一延伸二。以下我先提出几点重点,是面试官基本必问的问题,请一定要去了解! 基础知识...

    Vultr 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<