Android LruCache的源码解析

本文深入剖析了LRU缓存策略的原理及其在Android中的具体实现方式,详细介绍了LruCache类的创建、存入、读取和移除操作,并分析了其源码细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Lru是一种常见的置换策略,并不是Android所特有的。它的特点是置换近期最久未被访问的数据,下面来看看Android是怎么实现LRU缓存策略的。

创建

LruCache的创建的基本方法如下。

LruCache<Bitmap,String> mCache = new PictureCache((int) (Runtime.getRuntime().maxMemory() / 1024 / 8));

这里的使用的单位是KB,Cache的大小为应用内存的1/8,来看看源码。

private final LinkedHashMap<K, V> map;
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);
}

代码很简单,可以看到缓冲的数据是用LinkedHashMap来存储的,LinkedHashMap是有序的Map,通过一个链表来存储元素。先来看看这里LinkedHashMap的特点。

它的构造方法传入了true,说明它会根据各元素的访问次数调整其在链表中的位置。访问的元素会被放到链尾,如此一来近期访问少的元素就会渐渐靠近链首的位置。这个特点恰好符合LRU缓存策略的需要。

存入

mCache.put(url,bitmap);

这是存储的基本方法,来看看它的源码。

public final V put(K key, V value) {
		//1
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        //2
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
        	//3
            entryRemoved(false, key, previous, value);
        }

		//4
        trimToSize(maxSize);
        return previous;
    }

代码1先进行判空,合法输入才会继续。代码块2进入同步代码块,插入或更新缓存,如果是新存入的数据则把增加当前已用缓存大小。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;
}

如果缓存大小小于0,则不合法。还有一点需要注意,sizeOf方法默认返回1,指存入数据的大小,sizeOf返回的单位应该和LruCache存储单位一致,否则会导致错误。

代码3的entryRemoved方法是用来处理新旧数据的,默认是一个空方法。如果想要覆盖掉旧数据,则需要重写此方法。此外,有的数据由于是强引用,GC无法将其回收,我们也可以在这里对它们进行手动回收。

至此插入\更新数据就基本完成了,接下来到代码4,trimToSize方法是用来削减缓冲开销的。来看看做了什么。

private void trimToSize(int maxSize) {
		//A
        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!");
                }

				//1
                if (size <= maxSize) {
                    break;
                }

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

先进行合法判断,然后来到代码1,当前大小size小于缓冲容量则直接返回,也就是说只有在缓冲满了LruCache才会进行削减策略。刚刚说到LinkedHashMap会把近期访问的元素转移到链尾,近期访问少的元素渐渐靠近链首。代码2获取到链首元素也就是最近访问最少的,然后移除它,remove方法后面会分析。

整个方法是在一个死循环中进行的(代码块A),也就是说trimToSize方法会多次进行移除操作,直到size小于缓冲容量为止。

读取

bitmap = mCache.get(url);

缓存读取如上,看看get方法的源码。

public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        //1
        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.
         */

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

代码块1是同步代码块,读缓存就在里面完成,可见LruCache的访问操作是线程安全的。访问数据不为空则直接返回。否则到代码2创建缓冲数据。

create方法默认是返回null的。如果我们希望在访问为空时自动添加一个缓冲数据,则需要重写这个方法。接下的添加操作就和put方法差不多了。

移除

mCache.remove(url);

基本使用如上,来看看它的源码。

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

代码很简单,先进行合法检查,把它从LinkedHashMap中移除,必要时再手动回收它的内存。

参考文章

https://www.jianshu.com/p/471e03a5d9bf
https://www.jianshu.com/p/ef22974673ea

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值