关于universal-image-loader中的几种内存缓存策略
2015-11-11 17:03
381 查看
说到图片缓存,做android开发的人想必都很了解universal-image-loader这个开源框架,最近有时间把cache包里面各种缓存实现看了下,在这里对内存方面的缓存进行一个简单的总结。
首先了解一下里面主要的类:LimitedMemoryCache,基本上所有的缓存都是基于这个类进行的。它会根据你传进来的值进行缓存空间大小的设制,超过这个值之后将进行数据的清理,把内容删除到小于设定的值为止。核心代码如下:
@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()方法进行删除图片,每删除一次进行一次检查,直到总大小小于设定值的大小才进行缓存加载。对于这个removeNext抽象方法由子类来进行实现,删除的方式多种多样,可以按照各种算法随便发挥,了解到这个核思想之后,我们开始来介绍几种实现了这个类的缓存方案。
1.LRUMemoryCache: LRU(Least Recently Use),最近最少使用的缓存方式,就是缓存的大小超过一定的值之后,将会删除最近使用最少的那张图片。要使用这个缓存方式,我们先要了解一下LinkedHashMap,链式的哈西表,数据保存时使用的简和值都使用他们对应的哈西编码,然后使用一个双循环链表结构进行顺序维护,HashMap是一个散列存储,都是无序的,读取效率很高。LinkedHashMap在其基础上添加了一个双链表进行顺序的维护。然后我们的LRU算法也基本上都是这个存储结构实现。
private final Map<String, Bitmap> lruCache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(INITIAL_CAPACITY, LOAD_FACTOR, true));
要实现这个使用顺序维护,就要使用这个构造方法了,第一个参数是初始化容器大小,但是实际申请的大小空间会是这么一个关系:2的n次方>=INITIAL_CAPACITY , 在满足这个关系的前提下,取n的最小值。在INITIAL_CAPACITY==0的情况下比较特殊,这时候容器大小是2。LOAD_FACTOR是在容器中存放数量超过长度的这个比例时,容器将自动扩容为之前的两倍,第三个参数设置为true就是这个的关键了,把这个设置为true之后,每次在读取某一个容器的持有对象之后,这个持有的对象将会被放到尾部。
@Override public V get(Object key) { /* * This method is overridden to eliminate the need for a polymorphic * invocation in superclass at the expense of code duplication. */ if (key == null) { HashMapEntry<K, V> e = entryForNullKey; if (e == null) return null; if (accessOrder) makeTail((LinkedEntry<K, V>) e); return e.value; } int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { K eKey = e.key; if (eKey == key || (e.hash == hash && key.equals(eKey))) { if (accessOrder) //构造函数中传递进来的布尔参数 makeTail((LinkedEntry<K, V>) e); return e.value; } } return null; } private void makeTail(LinkedEntry<K, V> e) {//将访问的参数移动至链表末端。 // Unlink e e.prv.nxt = e.nxt; e.nxt.prv = e.prv; // Relink e as tail LinkedEntry<K, V> header = this.header; LinkedEntry<K, V> oldTail = header.prv; e.nxt = header; e.prv = oldTail; oldTail.nxt = header.prv = e; modCount++; }
方法中的accessorder就是你在构造函数里面传递进去的,只要这个是true,执行了get方法之后,就会把这个函数移动到链表的表尾,有了以上的基础,LRU似乎变得更加简单了。我们需要做的就是在每次缓存判断之后,如果需要删除图片引用,将链表中的第一个元素删除就可以了,因为最近使用的都会被移动到后面。
/** 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; } /** * Remove the eldest entries until the total of remaining entries is at or below the requested size. * * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements. */ 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); } } }
在每次图片放进去之后,都会使用trimToSize方法进行容器调整,调整到缓存里面的大小小于穿进来的参数,在这里穿进去的自然就是我们设置的缓存大小。
2.FIFOLimitedMemoryCache: 先进先出的一个策略。跟LRU的区别是,他每次都是简单的删除最先加入的元素,就是一个队列的形式,这个就没有上面的那个复杂了,这只需要一个List记录下保存的图片信息,每次删除队列中的第一个就行了:
@Override protected Bitmap removeNext() { return queue.remove(0); }
这个子类在这个方法的实现上就是这么简单的一个操作,删除第一个元素
3.FuzzyKeyMemoryCache:这个是一个key等价删除策略,在构造器中穿一个Comparator参数进来,重写public int compare(T lhs, T rhs);这个方法,这种缓存会把已经缓存的key和将要缓存的key进行自己实现定义的方式进行对比,如果出现了这种等价就删除掉第一个对应的entry。如果没有找到就不用删除。核心代码 :
@Override public boolean put(String key, Bitmap value) { // Search equal key and remove this entry synchronized (cache) { String keyToRemove = null; for (String cacheKey : cache.keys()) { if (keyComparator.compare(key, cacheKey) == 0) { keyToRemove = cacheKey; break; } } if (keyToRemove != null) { cache.remove(keyToRemove); } } return cache.put(key, value); }
4.LargestLimitedMemoryCache:这个缓存策略是在放入缓存前检测已经缓存的总容量,如果超过了这个值就删除掉占用内存最大的元素,这个就像是以前做的java基础体,在一个数组里面找出最大值一样:
@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; }
首先默认把第一个当作最小的,然后循环跟后面的进行比较,出现更大的就进行赋值,这样循环结束之后就可以得到占用内存最大的那个引用了。
5.LimitedAgeMemoryCache : 这个是一个时间限制的缓存策略,每次在引用一个对象到内存的时候,使用一个HashMap记录下来key和它存进去的时间,然后在访问缓存中的某张图片的时候,跟当前时间进行一个比较,如果这个两个时间的差值大于设置的缓存时间,就会把图片引用从缓存中删掉。
@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); }
第一个方法在保存的时候,同时保存当前时间。第二个方法在取出的时候,进行时间的检测。
6. UsingFreqLimitedMemoryCache : 在缓存超过限制之后,根据使用频率最少的引用进行优先的删除,这个是在保存引用的时候保存的是key和使用次数,放进去的时候记录次数为0,每次get调用次数加1,然后在调用removeNext的时候,找出使用次数最少的引用,跟上面找出占用内存最大的引用的方式基本相同。
@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; }
以上是对几种内存缓存方式的总结,之后将会推出磁盘缓存策略,算法核心是类似的,就是多了一个I/O操作。
相关文章推荐
- Android实现表情 抓取新浪表情
- 详解Android解析Xml的三种方式——DOM、SAX以及XMLpull
- IE7降低内存和降低CPU的几个技巧
- 如何高效的使用内存
- DOS下内存的配置
- XP/win2003下发现1G的内存比512M还慢的解决方法
- Enterprise Library for .NET Framework 2.0缓存使用实例
- PowerShell中编程清空IE缓存方法
- PowerShell中使用.NET将程序集加入全局程序集缓存
- PowerShell实现动态获取当前脚本运行时消耗的内存
- C#实现把dgv里的数据完整的复制到一张内存表的方法
- SQL语句实现查询SQL Server内存使用状况
- C#中缓存的基本用法总结
- C语言内存对齐实例详解
- 深入学习C语言中memset()函数的用法
- 全局变量与局部变量在内存中的区别详细解析
- wap开发中如何有效的利用缓存减少消息的传送量
- VB读取线程、句柄及写入内存的API代码实例
- PHP基于文件存储实现缓存的方法