关于universal-image-loader中的几种内存缓存策略

说到图片缓存,做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操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值