63.ImageLoader源代码分析-内存缓存算法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了63.ImageLoader源代码分析-内存缓存算法相关的知识,希望对你有一定的参考价值。

一. 前言

图片内存缓存可以提高图片显示速度,但是有些问题,比如占用内存,如果不加以控制,甚至可能会OOM

所以,需要提供各种各样的算法来控制内存的使用,以适应不同的使用场景,目前,ImageLoader提供了若干内存管理算法。

默认内存缓存是关闭的,需要手动打开

二. 继承关系图

技术分享图片

三. 主要内存算法介绍

算法 解释
MemoryCache Interface 内存缓存的接口
MemoryCache Interface 内存缓存的接口
FuzzyKeyMemoryCache 模糊Key内存缓存,一些不同的Key可能会被认为是相同(通过equals方法)。当你put一个值到cache里面时,equals相同的Key也会被替换。这是一个内部功能,通常我们不需要使用到它。
LimitedAgeMemoryCache 限制时间内存缓存,如果一些对象的使用时间超过了定义的值,那么就会移除。比如最大时间设置成60s,那么如果一个对象是60s前put进来的,那么它就会失效。
LruMemoryCache 一个强引用的图片,限制图片使用数量的内存缓存,每个被访问的图片都会移动到队列的头部。当一个图片添加到一个满的队列的时候,队尾的图片将会被驱逐而被垃圾回收器回收。
BaseMemoryCache 抽象类,基本的图片缓存管理,提供了图片存储(强引用或者弱引用),具体的可以看下面的MemoryCache实现。
WeakMemoryCache 继承自BaseMemoryCache,弱引用内存缓存,弱引用,当内存不足的时候,会被系统回收。
LimitedMemoryCache 抽象类,继承自BaseMemoryCache,限制内存的内存管理抽象类,包括下面4个具体实现。提供图片对象存储,存储的所有的图片大小将不会超过大小限制。这个缓存提供了强引用和弱引用存储了图片,强引用用来限制图片大小,弱引用用来其他缓存的图片。
FIFOLimitedMemoryCache 继承自LimitedMemoryCache,存储的图片大小不小超过限定的大小。当缓存图片到达指定大小时,会按照FIFO(先进先出)的原则清理图片。
LRULimitedMemoryCache 继承自LimitedMemoryCache,存储的图片大小不小超过限定的大小。当缓存图片到达指定大小时,最早使用的图片会从缓存中删除。
LargestLimitedMemoryCache 继承自LimitedMemoryCache,存储的图片大小不小超过限定的大小。当缓存图片到达指定大小时,最大的图片会从缓存中删除。
UsingFreqLimitedMemoryCache 继承自LimitedMemoryCache,存储的图片大小不小超过限定的大小。当缓存图片到达指定大小时,最少使用频率的图片会从缓存中删除。

下面,我们会具体看各个MemoryCache的实现

四. LimitedAgeMemoryCache

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

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

实现的方法很简单,在put的时候,用一个Map记录了存入的时间,get的时候,判断时间是否大于maxAge,如果是,那么就删掉这个缓存。

五. LruMemoryCache

public class LruMemoryCache implements MemoryCache {

    private final LinkedHashMap<String, Bitmap> map;

    private final int maxSize;

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

        synchronized (this) {
            size += sizeOf(key, value);
            Bitmap previous = map.put(key, value);
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
        }

        trimToSize(maxSize);
        return true;
    }

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

实现的原理是利用LinkedHashMap,先存入的key在遍历的时候在链表的头部,所以在put值的时候,从头开始遍历HashMap,从头删除图片缓存,直到内存的大小在指定大小以内。

另外为了防止多线程的问题,put和trimToSize删除图片的时候做了synchronized同步。

这个也是ImageLoader使用比较多的一种内存管理方法。

六. WeakMemoryCache

public class WeakMemoryCache extends BaseMemoryCache {
   @Override
   protected Reference<Bitmap> createReference(Bitmap value) {
      return new WeakReference<Bitmap>(value);
   }
}

实现方法很简单,用一个弱引用就可以解决,用弱引用包裹了图片。

七. FIFOLimitedMemoryCache

FIFOLimitedMemoryCache继承自抽象类LimitedMemoryCache,所以看算法实现前还要看LimitedMemoryCache代码。

抽象类LimitedMemoryCache和其实现子类的关系是LimitedMemoryCache put的时候限定超过大小时调用removeNext删除图片缓存;

但是具体怎么删,删除什么图片缓存,就由子类实现。

public abstract class LimitedMemoryCache extends BaseMemoryCache {

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

public class FIFOLimitedMemoryCache extends LimitedMemoryCache {
    private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>());
   ...

   @Override
   protected Bitmap removeNext() {
      return queue.remove(0);
   }
}

用list实现,直接从头开始移除。

八. LRULimitedMemoryCache

public class LRULimitedMemoryCache extends LimitedMemoryCache {

   /** Cache providing Least-Recently-Used logic */
   private final Map<String, Bitmap> lruCache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(INITIAL_CAPACITY, LOAD_FACTOR, true));

   @Override
   protected Bitmap removeNext() {
      Bitmap mostLongUsedValue = null;
      synchronized (lruCache) {
         Iterator<Entry<String, Bitmap>> it = lruCache.entrySet().iterator();
         if (it.hasNext()) {
            Entry<String, Bitmap> entry = it.next();
            mostLongUsedValue = entry.getValue();
            it.remove();
         }
      }
      return mostLongUsedValue;
   }
}

使用一个 LinkedHashMap lruCache来保存图片的put顺序,需要移除的时候(removeNext)从lruCache找到第一张图片即可。

九. UsingFreqLimitedMemoryCache

public class UsingFreqLimitedMemoryCache extends LimitedMemoryCache {

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

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

使用一个HashMap usingCounts来存储bitmap的使用次数,每次get的时候都会让使用次数+1,那需要移除的时候图片缓存的时候,遍历usingCounts,找到使用次数最少的图片,删除之。

不过这种遍历的算法感觉效率不高,慎用!

以上是关于63.ImageLoader源代码分析-内存缓存算法的主要内容,如果未能解决你的问题,请参考以下文章

64.ImageLoader源代码分析-磁盘命名和图片缓存算法

Android DiskLruCache 源代码解析 硬盘缓存的绝佳方案

ORACLE学习二

任务管理器 里那个选项是物理内存

多核cpu的频率怎么算?

分布式-技术专区-缓存系统问题分析