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

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

本片博客主要简介以下两个问题

  1. 介绍一下常见的内存缓存算法
  2. 怎样实现这些算法

大家应该对ImageLoader这个框架都不陌生吧,一个很强大的图片加载框架,虽然作者去年的时候已经停止维护了,但里面的许多东西还是值得我们去学习的。本篇博客讲解的内存缓存算法正是基于ImageLoader的实现基础之上的

常见的几种缓存算法

  • (1)LRU即Least RecentlyUsed,近期最少使用算法。

也就是当内存缓存达到设定的最大值时将内存缓存中近期最少使用的对象移除,有效的避免了OOM的出现。

  • (2)Least Frequently Used(LFU)

对每个缓存对象计算他们被使用的频率。把最不常用的缓存对象换走。

  • (3)、First in First out(FIFO)

这是一个低负载的算法,并且对缓存对象的管理要求不高。通过一个队列去跟踪所有的缓存对象,最近最常用的缓存对象放在后面,而更早的缓存对象放在前面,当缓存容量满时,排在前面的缓存对象会被踢走,然后把新的缓存对象加进去。

  • (4)、Simple time-based

通过绝对的时间周期去失效那些缓存对象。对于新增的对象,保存特定的时间。

  • (5)、LargestLimitedMemoryCache

超过指定缓存的话,每次移除栈最大内存的缓存的对象

下面我们一起来看一下ImageLoader是怎样实现这些算法的

首先我们一起先来看一下类UML图

源码分析

  • 1)首先我们先来看一下BaseMemoryCache做了什么?
public abstract class BaseMemoryCache implements MemoryCache {

    /** Stores not strong references to objects */
    private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(new HashMap<String, Reference<Bitmap>>());

    @Override
    public Bitmap get(String key) {
        Bitmap result = null;
        Reference<Bitmap> reference = softMap.get(key);
        if (reference != null) {
            result = reference.get();
        }
        return result;
    }

    @Override
    public boolean put(String key, Bitmap value) {
        softMap.put(key, createReference(value));
        return true;
    }

    @Override
    public Bitmap remove(String key) {
        Reference<Bitmap> bmpRef = softMap.remove(key);
        return bmpRef == null ? null : bmpRef.get();
    }

    @Override
    public Collection<String> keys() {
        synchronized (softMap) {
            return new HashSet<String>(softMap.keySet());
        }
    }

    @Override
    public void clear() {
        softMap.clear();
    }

    /** Creates {@linkplain Reference not strong} reference of value */
    protected abstract Reference<Bitmap> createReference(Bitmap value);
}

其实就是保存着一份弱引用而已,而它的父类Memory只是定义了几个接口方法,统一标准而已

  • 2)接着我们来看LimitedMemoryCache做了什么?
public abstract class LimitedMemoryCache extends BaseMemoryCache {

    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;

    private final AtomicInteger cacheSize;

    /**
     * Contains strong references to stored objects. Each next object is added last. If hard cache size will exceed
     * limit then first object is deleted (but it continue exist at {@link #softMap} and can be collected by GC at any
     * time)
     */
    private final List<Bitmap> hardCache = Collections.synchronizedList(
                                                         new LinkedList<Bitmap>());

    /** @param sizeLimit Maximum size for cache (in bytes) */
    public LimitedMemoryCache(int sizeLimit) {
        this.sizeLimit = sizeLimit;
        cacheSize = new AtomicInteger();
        if (sizeLimit > MAX_NORMAL_CACHE_SIZE) {
            L.w("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) {
                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;
    }

    @Override
    public Bitmap remove(String key) {
        Bitmap value = super.get(key);
        if (value != null) {
            if (hardCache.remove(value)) {
                cacheSize.addAndGet(-getSize(value));
            }
        }
        return super.remove(key);
    }

    @Override
    public void clear() {
        hardCache.clear();
        cacheSize.set(0);
        super.clear();
    }

    protected int getSizeLimit() {
        return sizeLimit;
    }

    protected abstract int getSize(Bitmap value);

    protected abstract Bitmap removeNext();
}

LimitedMemoryCache所做的工作可以分为以下几步

  • 1) 保存着一份强引用

    private final List hardCache = Collections.synchronizedList(

    new LinkedList());

  • 2) 其实在我们调用put方法的时候,即我们把bitmap存进内存的时候,他会判断是否超出我们的最大值,超出我们的最大值就会调用removeNext();来获得我们将要移除的bitmap对象,最终再调用hardCache.remove(removedValue)去移除它。
  • 3) 注意到removeNext()方法是抽象方法,交给子类自己去实现自己的算法逻辑。

注意事项

结合BaseMemoryCache和LimitedMemoryCache,我们可以知道LimitedMemoryCache的子类,至少保存着两份引用,一份是强引用,一份是弱引用

//父类BaseMemoryCache的成员变量,并且每次在操作的时候都会把bitmap的弱引用存进去
 private final Map<String, Reference<Bitmap>> softMap = Collections.synchronizedMap(
                new HashMap<String, Reference<Bitmap>>());

//LimitedMemoryCache的成员变量,缓存的bitmap是强引用
private final List<Bitmap> hardCache = Collections.synchronizedList(new LinkedList<Bitmap>());

有人可能会有疑问了这些成员变量不是私有的吗?为什么说LimitedMemoryCache的子类,至少保存着两份引用,这点我们可以从他们的put方法中知道

@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<Bitmap> createReference(Bitmap value) {
        return new WeakReference<Bitmap>(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对象的?
    
    ```java
    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<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>());

    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<Bitmap> createReference(Bitmap value) {
        return new WeakReference<Bitmap>(value);
    }
}
  • 1)从上面的分析当中我们可以知道主要关心put和removeNext()这两个方法就可以了,put()方法其实就是把bitmap对象存进我们的queue队列中
  • 2)remove方法其实就是一出队列的第一个bitmap对象,将先进先出,符合我们的FIFO原则
    @Override
    public Bitmap get(String key) {
    Bitmap result = null;
    Reference<Bitmap> 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);
      }
   }
}
/**
 * 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 <b>(in seconds)</b>. 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<String> 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<String> keys() {
        synchronized (mMap) {
            return new HashSet<String>(mMap.keySet());
        }
    }

    protected abstract int getSize(Bitmap value);

    protected abstract String removeNext();
}

LimitedMemoryCache所做的工作可以分为以下几步

  • 1) 保存着一份强引用
//在构造方法里面初始化
private final Map<String, Bitmap> mMap;
  • 2) 在我们调用put方法的时候,即我们把bitmap存进内存的时候,他会判断是否超出我们的最大值,超出我们的最大值就会调用removeNext();来获得我们将要移除的bitmap对象,最终再调用hardCache.remove(removedValue)去移除它。
  • 3) 注意到removeNext()方法是抽象方法,交给子类自己去实现自己的算法逻辑,返回类型是String。

下面我们来看一下

public class UsingFreqLimitedMemoryCache extends LimitedMemoryCache {

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

//这里省略了若干个方法
    @Override
    public boolean put(String key, Bitmap value) {
        if (super.put(key, value)) {
            usingCounts.put(key, 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(key, usageCount + 1);
            }
        }
        return value;
    }

    @Override
    public Bitmap remove(String key) {
        usingCounts.remove(key);
        return super.remove(key);
    }

    @Override
    protected String removeNext() {
        Integer minUsageCount = null;
        String leastUsedValue = null;
        Set<Entry<String, Integer>> entries = usingCounts.entrySet();
        synchronized (usingCounts) {
            for (Entry<String, 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;
    }

}

与ImageLoader不同的是,我们是用这个记录

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

而ImageLoader是用这种类型的记录,其他的基本大同小异,有兴趣的可以去这里下载源码

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

题外话:通过这篇博客,学习了更多的缓存算法,同时你们有没有发现,很多地方都用到了Collection框架,而要用好这些,个人觉得去了解他们的原理是非常必要的,尤其是map和List集合,不管说是初学者还是大牛,毕竟万丈高楼也是从平地盖起的,基础非常重要

转载请注明原博客地址: http://blog.csdn.net/gdutxiaoxu/article/details/51914000

源码参考地址 ImagerLoader:https://github.com/nostra13/Android-Universal-Image-Loader

源码下载地址: https://github.com/gdutxiaoxu/library_algorithm.git

时间: 2024-10-10 21:18:06

带你了解Android常见的内存缓存算法的相关文章

android中的内存泄露查找与常见的内存泄露案例分析

常见的内存泄露查找方法请参见:http://hukai.me/android-performance-patterns/ 这篇文章是google发布的android性能优化典范示例,对于渲染.内存GC与电量消耗都做了好的示范. 这里我总结了下,android中常见的内存泄露 1.类中调用registerReceiver后未调用unregisterReceiver(). 在调用registerReceiver后,若未调用unregisterReceiver,其所占的内存是相当大的. 这种情况常见于

Android 性能篇 -- 带你领略Android内存泄漏的前世今生

基础了解 什么是内存泄漏? 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗.内存泄漏并不是指物理上的内存消失,这里的内存泄漏是指由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费. Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是 静态分配 . 栈式分配 和 堆式分配 ,对应的三种存储策略使用的内存空间主要分别是 静态存储区(也称方法区) . 栈区 和 堆区 . ?? 静态存储区(方法区):主要存放 静态数据 . 全局

Android 常见内存泄露 &amp; 解决方案

前言 内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃 (OOM) 等严重后果. 那什么情况下不能被回收呢? 目前 java 垃圾回收主流算法是虚拟机采用 GC Roots Tracing 算法.算法的基本思路是:通过一系列的名为 GC Roots (GC 根节点)的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径,当一个对象到GC Roots没有任何引用链相连(图论说:从GC Roots

Android开发 |常见的内存泄漏问题及解决办法

在Android开发中,内存泄漏是比较常见的问题,有过一些Android编程经历的童鞋应该都遇到过,但为什么会出现内存泄漏呢?内存泄漏又有什么影响呢? 在Android程序开发中,当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了. 内存泄漏有什么影响呢?它是造成应用程序OOM的主要原因之一.由于Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时

Android常见内存泄露

前言 对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary.MAT等工具来检测应用程序是否存在内存泄漏,MAT是一款强大的内存分析工具,功能繁多而复杂,而LeakCanary则是由Square开源的一款轻量第三方内存泄漏检测工具,当它检测到程序中有内存泄漏的产生时,它将以最直观的方式告诉我们该内存泄漏是由谁产生的和该内存泄漏导致谁泄漏了而不能回收,供我们复查. 内存泄漏 为

Android内存优化之内存缓存

前言: 上面两篇博客已经讲了图片的基本知识和图片的加载方法及优化,所有的这些优化都是为了避免应用出现OOM这个问题.一个好的应用程序不仅要健壮不能出错还要方便用户使用,对于用户来说你的应用不仅要美观还要流畅,很快的呈现给他想要的.很快的加载图片除了加载的优化外还需要缓存,下面这篇博客将会讲图片缓存. 什么是缓存? 缓存技术原理就是把用户访问的所有对象看作一个全集,经过算法标记哪些是用户经常访问的对象,把这些对象放到一个集合里,这个集合是全集一个子集,下一次用户再访问的时候会先从这个子集集合中查找

Android Glide数据更新及内存缓存、硬盘缓存清理

[转] 原文                                         Android Glide数据更新及内存缓存.硬盘缓存清理 Android的Glide在加载图片时候内部默认使用了缓存机制,Glide的缓存机制分为两级,第一级是内存缓存,然后第二级是硬盘缓存.缓存的过程首先是在内存中缓存,然后将加载的图片资源缓存到硬盘,这样就可以在随后的再次加载中使用缓存了,Glide使用缓存时候首先要检查内存这一层级是否缓存了相应的缓存,如果有,则直接使用,如果没有,则深入到硬盘缓

Android性能优化之常见的内存泄漏

前言 对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary.MAT等工具来检测应用程序是否存在内存泄漏,MAT是一款强大的内存分析工具,功能繁多而复杂,而LeakCanary则是由Square开源的一款轻量第三方内存泄漏检测工具,当它检测到程序中有内存泄漏的产生时,它将以最直观的方式告诉我们该内存泄漏是由谁产生的和该内存泄漏导致谁泄漏了而不能回收,供我们复查. 最近腾讯bu

Android中常见的内存泄漏

为什么会产生内存泄漏? 当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏. 内存泄漏对程序的影响? 内存泄漏是造成应用程序OOM的主要原因之一!我们知道Android系统为每个应用程序分配的内存有限,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash. Android中常见的内存泄漏汇总 1