带你了解Android常见的内存缓存算法

有人可能会有疑问了这些成员变量不是私有的吗?为什么说LimitedMemoryCache的子类,至少可以访问两份引用,这点我们可以从他们的put方法和get方法中知道,只需要调用super.put()即可把我们的bitmap缓存存到父类,调用super.get()即可从父类中 访问我们保存的Bitmap对象 。

@Override

public boolean put(String key, Bitmap value) {

boolean putSuccessfully = false;

// Try to add value to hard cache

int valueSize = getSize(value);

int sizeLimit = getSizeLimit();

int curCacheSize = cacheSize.get();

if (valueSize < sizeLimit) {

while (curCacheSize + valueSize > sizeLimit) {

Bitmap removedValue = removeNext();

if (hardCache.remove(removedValue)) {

curCacheSize = cacheSize.addAndGet(-getSize(removedValue));

}

}

hardCache.add(value);

cacheSize.addAndGet(valueSize);

putSuccessfully = true;

}

// Add value to soft cache

super.put(key, value);

return putSuccessfully;

}

同理LimitedMemoryCache的子类put也会调用LimitedMemoryCache的put方法,代码见下面分析。

同时从上面的分析当中我们可以知道主要关心put和removeNext()这两个方法就可以了,put()方法其实就是把bitmap对象存进我们的queue队列中

下面我们在看一下UsingFreqLimitedMemoryCache是怎样实现的?

public class UsingFreqLimitedMemoryCache extends LimitedMemoryCache {

/**

  • Contains strong references to stored objects (keys) and last object usage date (in milliseconds). If hard cache

  • size will exceed limit then object with the least frequently usage is deleted (but it continue exist at

  • {@link #softMap} and can be collected by GC at any time)

*/

private final Map<Bitmap, Integer> usingCounts = Collections.synchronizedMap(new HashMap<Bitmap, Integer>());

public UsingFreqLimitedMemoryCache(int sizeLimit) {

super(sizeLimit);

}

@Override

public boolean put(String key, Bitmap value) {

if (super.put(key, value)) {

usingCounts.put(value, 0);

return true;

} else {

return false;

}

}

@Override

public Bitmap get(String key) {

Bitmap value = super.get(key);

// Increment usage count for value if value is contained in hardCahe

if (value != null) {

Integer usageCount = usingCounts.get(value);

if (usageCount != null) {

usingCounts.put(value, usageCount + 1);

}

}

return value;

}

@Override

public Bitmap remove(String key) {

Bitmap value = super.get(key);

if (value != null) {

usingCounts.remove(value);

}

return super.remove(key);

}

@Override

public void clear() {

usingCounts.clear();

super.clear();

}

@Override

protected int getSize(Bitmap value) {

return value.getRowBytes() * value.getHeight();

}

@Override

protected Bitmap removeNext() {

Integer minUsageCount = null;

Bitmap leastUsedValue = null;

Set<Entry<Bitmap, Integer>> entries = usingCounts.entrySet();

synchronized (usingCounts) {

for (Entry<Bitmap, Integer> entry : entries) {

if (leastUsedValue == null) {

leastUsedValue = entry.getKey();

minUsageCount = entry.getValue();

} else {

Integer lastValueUsage = entry.getValue();

if (lastValueUsage < minUsageCount) {

minUsageCount = lastValueUsage;

leastUsedValue = entry.getKey();

}

}

}

}

usingCounts.remove(leastUsedValue);

return leastUsedValue;

}

@Override

protected Reference createReference(Bitmap value) {

return new WeakReference(value);

}

}

思路解析
  1. 当我们调用put方法,把bitmap存进内存的时候,他会判断是否超出我们的最大值,超出我们的最大值就会调用removeNext();来获得我们将要移除的bitmap对象,最终再调用hardCache.remove(removedValue)去移除它。

@Override

public boolean put(String key, Bitmap value) {

boolean putSuccessfully = false;

// Try to add value to hard cache

int valueSize = getSize(value);

int sizeLimit = getSizeLimit();

int curCacheSize = cacheSize.get();

if (valueSize < sizeLimit) {

while (curCacheSize + valueSize > sizeLimit) {

Bitmap removedValue = removeNext();

if (hardCache.remove(removedValue)) {

curCacheSize = cacheSize.addAndGet(-getSize(removedValue));

}

}

hardCache.add(value);

cacheSize.addAndGet(valueSize);

putSuccessfully = true;

}

// Add value to soft cache

super.put(key, value);

return putSuccessfully;

}

···

  • 下面我们来看一下removeNext()是怎样获得将要移除的bitmap对象的?

private final Map<Bitmap, Integer> usingCounts = Collections.

synchronizedMap(new HashMap<Bitmap, Integer>());

@Override

protected Bitmap removeNext() {

Integer minUsageCount = null;

Bitmap leastUsedValue = null;

Set<Entry<Bitmap, Integer>> entries = usingCounts.entrySet();

synchronized (usingCounts) {

for (Entry<Bitmap, Integer> entry : entries) {

if (leastUsedValue == null) {

leastUsedValue = entry.getKey();

minUsageCount = entry.getValue();

} else {

Integer lastValueUsage = entry.getValue();

if (lastValueUsage < minUsageCount) {

minUsageCount = lastValueUsage;

leastUsedValue = entry.getKey();

}

}

}

}

usingCounts.remove(leastUsedValue);

return leastUsedValue;

}

其实就是将usingCounts中出现次数最少的节点移除掉。

那它实在什么时候计算bitmap的使用次数的呢?相信大多数人会想到,既然是使用频率,那肯定是在取图片的过程中计算的,没错,下面让我们一起来看一下是怎样实现的?

@Override

public Bitmap get(String key) {

Bitmap value = super.get(key);

// Increment usage count for value if value is contained in hardCahe

if (value != null) {

Integer usageCount = usingCounts.get(value);

if (usageCount != null) {

usingCounts.put(value, usageCount + 1);

}

}

return value;

}

其实也很简单,判断是否存在缓存value,存在的话,使用次数加一

好的,到此UsingFreqLimitedMemoryCache的源码分析位置


FIFOLimitedMemoryCache源码分析


public class FIFOLimitedMemoryCache extends LimitedMemoryCache {

private final List queue = Collections.synchronizedList(new LinkedList());

public FIFOLimitedMemoryCache(int sizeLimit) {

super(sizeLimit);

}

@Override

public boolean put(String key, Bitmap value) {

if (super.put(key, value)) {

queue.add(value);

return true;

} else {

return false;

}

}

@Override

public Bitmap remove(String key) {

Bitmap value = super.get(key);

if (value != null) {

queue.remove(value);

}

return super.remove(key);

}

@Override

public void clear() {

queue.clear();

super.clear();

}

@Override

protected int getSize(Bitmap value) {

return value.getRowBytes() * value.getHeight();

}

@Override

protected Bitmap removeNext() {

return queue.remove(0);

}

@Override

protected Reference createReference(Bitmap value) {

return new WeakReference(value);

}

}

  • 1)从上面的分析当中我们可以知道主要关心put和removeNext()这两个方法就可以了,put()方法其实就是把bitmap对象存进我们的queue队列中

  • 2)remove方法其实就是一出队列的第一个bitmap对象,将先进先出,符合我们的FIFO原则

@Override

public Bitmap get(String key) {

Bitmap result = null;

Reference reference = softMap.get(key);

if (reference != null) {

result = reference.get();

}

return result;

}

LargestLimitedMemoryCache源码分析


public class LargestLimitedMemoryCache extends LimitedMemoryCache {

/**

  • Contains strong references to stored objects (keys) and sizes of the objects. If hard cache

  • size will exceed limit then object with the largest size is deleted (but it continue exist at

  • {@link #softMap} and can be collected by GC at any time)

*/

private final Map<Bitmap, Integer> valueSizes = Collections.synchronizedMap(new HashMap<Bitmap, Integer>());

public LargestLimitedMemoryCache(int sizeLimit) {

super(sizeLimit);

}

@Override

public boolean put(String key, Bitmap value) {

if (super.put(key, value)) {

valueSizes.put(value, getSize(value));

return true;

} else {

return false;

}

}

@Override

public Bitmap remove(String key) {

Bitmap value = super.get(key);

if (value != null) {

valueSizes.remove(value);

}

return super.remove(key);

}

//这里我们省略若干个方法,有兴趣的话讲源码去,下面有提供源码下载地址

@Override

protected Bitmap removeNext() {

Integer maxSize = null;

Bitmap largestValue = null;

Set<Entry<Bitmap, Integer>> entries = valueSizes.entrySet();

synchronized (valueSizes) {

for (Entry<Bitmap, Integer> entry : entries) {

if (largestValue == null) {

largestValue = entry.getKey();

maxSize = entry.getValue();

} else {

Integer size = entry.getValue();

if (size > maxSize) {

maxSize = size;

largestValue = entry.getKey();

}

}

}

}

valueSizes.remove(largestValue);

return largestValue;

}

}

同样我们只关心put方法和removeNext()方法

@Override

public boolean put(String key, Bitmap value) {

if (super.put(key, value)) {

valueSizes.put(value, getSize(value));

return true;

}else {

return false;

}

}

@Override

protected Bitmap removeNext() {

Integer maxSize = null;

Bitmap largestValue = null;

Set<Entry<Bitmap, Integer>> entries = valueSizes.entrySet();

synchronized (valueSizes) {

for (Entry<Bitmap, Integer> entry : entries) {

if (largestValue == null) {

largestValue = entry.getKey();

maxSize = entry.getValue();

} else {

Integer size = entry.getValue();

if (size > maxSize) {

maxSize = size;

largestValue = entry.getKey();

}

}

}

}

valueSizes.remove(largestValue);

return largestValue;

}

  • 1)其实就是put方法的时候( valueSizes.put(value, getSize(value));),我们将bitmap做为key,大小作为value,存进valueSizesM集合

  • 2)在超过最大缓存数量的时候,遍历移除掉valueSizes中最大的bitmap。

下面我们来看一下LruMemoryCache是怎样实现的

源码我们就不贴出来了

主要逻辑在put方法中

// 存储bitmap对象,在构造方法里面初始化

private final LinkedHashMap<String, Bitmap> map;

/** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue. */

@Override

public final boolean put(String key, Bitmap value) {

if (key == null || value == null) {

throw new NullPointerException(“key == null || value == null”);

}

synchronized (this) {

size += sizeOf(key, value);

Bitmap previous = map.put(key, value);

if (previous != null) {

size -= sizeOf(key, previous);

}

}

trimToSize(maxSize);

return true;

}

当我们把bitmap存进内存的时候,他会trimToSize(maxSize)这个方法去判断我们是否超过我们规定内存的最大值,超过的话移除掉最先添加进来的那个

private void trimToSize(int maxSize) {

while (true) {

String key;

Bitmap value;

synchronized (this) {

if (size < 0 || (map.isEmpty() && size != 0)) {

throw new IllegalStateException(getClass().getName() + “.sizeOf() is reporting inconsistent results!”);

}

if (size <= maxSize || map.isEmpty()) {

break;

}

Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();

if (toEvict == null) {

break;

}

key = toEvict.getKey();

value = toEvict.getValue();

map.remove(key);

size -= sizeOf(key, value);

}

}

}


下面我们来看一下LimitedAgeMemoryCache 是怎样实现的

/**

  • Decorator for {@link MemoryCache}. Provides special feature for cache: if some cached object age exceeds defined

  • value then this object will be removed from cache.

  • 采用装饰着模式,计算对象的最大存活时间

  • 在get方法的时候判断大于的移除掉

*/

public class LimitedAgeMemoryCache implements MemoryCache {

private final MemoryCache cache;

private final long maxAge;

private final Map<String, Long> loadingDates = Collections.synchronizedMap(new HashMap<String, Long>());

/**

  • @param cache Wrapped memory cache

  • @param maxAge Max object age (in seconds). If object age will exceed this value then it’ll be removed from

  •           cache on next treatment (and therefore be reloaded).
    

*/

public LimitedAgeMemoryCache(MemoryCache cache, long maxAge) {

this.cache = cache;

this.maxAge = maxAge * 1000; // to milliseconds

}

@Override

public boolean put(String key, Bitmap value) {

boolean putSuccesfully = cache.put(key, value);

if (putSuccesfully) {

loadingDates.put(key, System.currentTimeMillis());

}

return putSuccesfully;

}

@Override

public Bitmap get(String key) {

Long loadingDate = loadingDates.get(key);

if (loadingDate != null && System.currentTimeMillis() - loadingDate > maxAge) {

cache.remove(key);

loadingDates.remove(key);

}

return cache.get(key);

}

@Override

public Bitmap remove(String key) {

loadingDates.remove(key);

return cache.remove(key);

}

@Override

public Collection keys() {

return cache.keys();

}

@Override

public void clear() {

cache.clear();

loadingDates.clear();

}

}

分析
  • 1)这个采用了装饰者模式,包装我们的Memory对象,不了解装饰者模式的,建议先读我的这一篇博客装饰者模式及其应用***:http://blog.csdn.net/gdutxiaoxu/article/details/51885105***

  • 2)主要逻辑在get方法里面,在我们通过key取bitmap的时候,他会先判断存活时间是否超出我们规定的maxAge(System.currentTimeMillis() - loadingDate > maxAge),超过的话移除掉

  • 3)那我们是怎样保存这些的存活时间的呢,其实很简单?就是用一个loadingDates集合来保存,在我们put的时候,把当前的时间存进去,源码体现如下

//成员变量,保持存活时间的map集合

private final Map<String, Long> loadingDates = Collections.synchronizedMap(

new HashMap<String, Long>());

@Override

public boolean put(String key, Bitmap value) {

boolean putSuccesfully = cache.put(key, value);

if (putSuccesfully) {

loadingDates.put(key, System.currentTimeMillis());

}

return putSuccesfully;

}


感觉ImageLoader在实现FIFOLimitedMemoryCache算法的时候还是有一点缺陷,为什么呢?

  • 1)我们看了FIFOLimitedMemoryCache,LimitedMemoryCache,里面的方法发现并没没有重写get方法,只有BaseMemoryCache类有实现get方法,那这样就会导致其实我们去缓存bitmap的时候,都会从softMap里面去取,并没有从我们的队列里面去取,我们知道,当内存紧张的时候,会优先回收弱引用引用的对象,有可能发生这样的情况,弱引用用已经被回收了,但是我们的queue里面的强引用还没有回收。再者内存中同时保存着弱引用和强引用,相对来说也是比较占内存的(有错误的话欢迎指出)

  • 2)个人感觉没有必要使用双引用了,弱应用和强引用,使用其中的一种就可以了,当然ImageLoader的LruCache实现就内存当中bitmap的缓存只保存着一份引用。

到此ImageLoader内存的缓存算法源码分析为止,下面我稍微改一下实现方式,内存里面不再保存着两份引用了,bitmap的缓存只保存着一份引用。


自己实现的内存缓存算法


类UML图

其实跟ImageLoader实现的也没有多大区别,只是我去除了弱引用,每个实现类里面不像LimitedMemoryCache的实现类一样持有两份引用而已

首先我们来看一下LimitedMemoryCache是怎样实现的

public abstract class LimitedMemoryCache implements MemoryCache {

private static final int MAX_NORMAL_CACHE_SIZE_IN_MB = 16;

private static final int MAX_NORMAL_CACHE_SIZE = MAX_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024;

private final int sizeLimit;

public static final String TAG=“tag”;

private final AtomicInteger cacheSize;

private final Map<String, Bitmap> mMap= Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>());

public LimitedMemoryCache(int sizeLimit) {

this.sizeLimit = sizeLimit;

cacheSize = new AtomicInteger();

if (sizeLimit > MAX_NORMAL_CACHE_SIZE) {

Log.w(TAG,“You set too large memory cache size (more than %1$d Mb)”+ MAX_NORMAL_CACHE_SIZE_IN_MB);

}

}

@Override

public boolean put(String key, Bitmap value) {

boolean putSuccessfully = false;

// Try to add value to hard cache

int valueSize = getSize(value);

int sizeLimit = getSizeLimit();

int curCacheSize = cacheSize.get();

if (valueSize < sizeLimit) {

while (curCacheSize + valueSize > sizeLimit) {

String removeKey = removeNext();

if(removeKey==null){

break;

}

Bitmap bitmap = mMap.remove(key);

if(bitmap!=null){

curCacheSize = cacheSize.addAndGet(-getSize(bitmap));

}

}

mMap.put(key,value);

cacheSize.addAndGet(valueSize);

putSuccessfully = true;

}

return putSuccessfully;

}

@Override

public Bitmap remove(String key) {

return mMap.remove(key);

}

@Override

public Bitmap get(String key) {

return mMap.get(key);

}

@Override

public void clear() {

mMap.clear();

cacheSize.set(0);

}

protected int getSizeLimit() {

return sizeLimit;

}

@Override

public Collection keys() {

synchronized (mMap) {

return new HashSet(mMap.keySet());

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
cheSize = cacheSize.addAndGet(-getSize(bitmap));

}

}

mMap.put(key,value);

cacheSize.addAndGet(valueSize);

putSuccessfully = true;

}

return putSuccessfully;

}

@Override

public Bitmap remove(String key) {

return mMap.remove(key);

}

@Override

public Bitmap get(String key) {

return mMap.get(key);

}

@Override

public void clear() {

mMap.clear();

cacheSize.set(0);

}

protected int getSizeLimit() {

return sizeLimit;

}

@Override

public Collection keys() {

synchronized (mMap) {

return new HashSet(mMap.keySet());

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-OUaSpNNd-1715901511341)]

[外链图片转存中…(img-HMSQtBTp-1715901511342)]

[外链图片转存中…(img-gUFtw2gI-1715901511342)]

[外链图片转存中…(img-bKYg1qKO-1715901511343)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值