通过源码理解LruCache的实现

本文详细介绍了LRU缓存机制的实现原理及其核心方法,包括构造方法、清理方法、重新调整大小方法等,并深入剖析了get()、put()、remove()等关键操作。

LRU的全称是Least Recently Used,表示最近最少使用,它的实现主要是靠内部的一个LinkedHashMap来实现的,它内部维护了一个LinkedHashMap对象集合,所有的数据都是保存在该集合中的。

LruCache是内存缓存,默认情况它缓存的数据都是强引用的,如果需要使用在本地磁盘缓存的话,可以使用DiskLruCache来实现。

下面通过阅读源码来理解LruCache的实现。

1、构造方法

public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    //最近最少使用本身是LinkedHashMap中实现的,true表示的就是按照最近最少使用来排序插入,如果为false,那么它就是一个普通的map集合,按照顺序来插入的
    this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

我们在构造一个LruCache对象的时候,需要传递一个maxSize,该值表示当前能够在内存中缓存的最大数据,如果数据超过了该限制,那么就会调用trimToSize(maxSize)方法来清除最近没有使用的对象在腾出一些内存空间出来。

2、清理方法trimToSize()

public void trimToSize(int maxSize) {
    while (true) {
        K key;
        V value;
        synchronized (this) {
            //如果当前缓存的数据大小小于0或者缓存的数据大小大于0但是map集合是空的话,就抛出异常,一般来说不会出现这种情况。
            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中移除该对象
            map.remove(key);
            //重新计算size大小
            size -= safeSizeOf(key, value);
            //evictionCount表示被移除的对象数量,此处++
            evictionCount++;
        }
        //最后调用entryRemoed()方法,默认是一个空实现。
        entryRemoved(true, key, value, null);
    }
}

trimToSize()方法用来清理当前最近没有使用的对象数据,如果当前已经缓存的数据超过了要求缓存的最大限制,那么就会循环的来清理对象直到当前缓存的数据大小小于最大限制。在清理完每一次最近没有使用的对象后就会调用entryRemoved(),它默认是一个空实现,在一些特殊情况我们可以实现此方法来做一些额外的事情,比如当我们缓存的bitmap对象被回收了,那么就可以在此方法中对bitmap调用recycle()方法来做一些额外的操作。

3、重新调整大小resize()

public void resize(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }

    synchronized (this) {
        this.maxSize = maxSize;
    }
    trimToSize(maxSize);
}

resize()方法可以对当前lruCache对象重新调整大小,当设置的大小比原来要小的话就会在trimToSize()中清理内存。

4、get()

public final V get(K key) {
    //如果key值为空则抛出异常
    if (key == null) {
        throw new NullPointerException("key == null");
    }

    V mapValue;
    synchronized (this) {
        mapValue = map.get(key);
        //如果在map中找到就直接方法
        if (mapValue != null) {
            //hitCount表示命中数,即当前找到的总共次数
            hitCount++;
            return mapValue;
        }
        missCount++;
    }
    //如果map中没有的话就调用create方法来创建一个,默认create()返回null
    V createdValue = create(key);
    //如果没有创建就直接返回
    if (createdValue == null) {
        return null;
    }

    synchronized (this) {
        //创建的总次数加1
        createCount++;
        //将创建的值放入到map集合中,返回原来的值
        mapValue = map.put(key, createdValue);

        if (mapValue != null) {
            //如果集合中有值了那就将原来的值放回去,不用新创建的值
            map.put(key, mapValue);
        } else {
            //如果原来就没有值的话那么重新计算当前缓存的总大小
            size += safeSizeOf(key, createdValue);
        }
    }
    //如果map中已经有值了那么久调用etryRemoved()方法,并将原来的值返回
    if (mapValue != null) {
        entryRemoved(false, key, createdValue, mapValue);
        return mapValue;
    } else {
        //如果是新创建的值,那么需要触发调整内存空间并将其返回
        trimToSize(maxSize);
        return createdValue;
    }
}

如果在map中没有获取到的话,会调用create()方法来创建key对应的value值,默认返回null,所以在使用的时候可以实现该方法来返回。有一点需要注意的是,在第一次从map中获取mapValue为null的情况下,在调用create()方法后又重新判断了mapValue是否为null的原因是,在多线程的情况下,第一次获取为null,在退出第一个synchronized后有可能别的线程就重新添加了该key对应的value进入,所以在第二个synchronized中需要重新判断mapValue是否为null来防止冲突。
5、put()

public final V put(K key, V value) {
    //如果传入的key和value为null就抛出异常
    if (key == null || value == null) {
        throw new NullPointerException("key == null || value == null");
    }

    V previous;
    synchronized (this) {
        putCount++;
        //重新调整当前缓存的数据大小
        size += safeSizeOf(key, value);
        //将传入的值添加进map集合中,返回原来的值
        previous = map.put(key, value);
        if (previous != null) {
            //如果原来有值就重新调整大小
            size -= safeSizeOf(key, previous);
        }
    }

    if (previous != null) {
        //如果原来的值不为null,则调用entryRemoved()让子类来处理额外的操作
        entryRemoved(false, key, previous, value);
    }
    //触发清理缓存
    trimToSize(maxSize);
    //将原来的值返回
    return previous;
}

6、remove()

public final V remove(K key) {
    //如果key值为null就抛出异常
    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()让子类来处理
        entryRemoved(false, key, previous, null);
    }

    return previous;
}

7、计算value大小sizeOf()

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

默认情况下sizeOf()返回的都是1,用于获取每一个value对象所占用的内存大小,所以我们在使用的时候一般都需要覆写该方法来返回value的真实的值,比如缓存的是bitmap,那么这里就需要返回一张bitmap占用的内存大小。

LRUCache的源码大致就这么多,其实并不复杂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值