溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

63.ImageLoader源代碼分析-內(nèi)存緩存算法

發(fā)布時(shí)間:2020-08-09 05:59:34 來源:網(wǎng)絡(luò) 閱讀:464 作者:rongwei84n 欄目:軟件技術(shù)

一. 前言

圖片內(nèi)存緩存可以提高圖片顯示速度,但是有些問題,比如占用內(nèi)存,如果不加以控制,甚至可能會OOM

所以,需要提供各種各樣的算法來控制內(nèi)存的使用,以適應(yīng)不同的使用場景,目前,ImageLoader提供了若干內(nèi)存管理算法。

默認(rèn)內(nèi)存緩存是關(guān)閉的,需要手動(dòng)打開

二. 繼承關(guān)系圖

63.ImageLoader源代碼分析-內(nèi)存緩存算法

三. 主要內(nèi)存算法介紹

算法 解釋
MemoryCache Interface 內(nèi)存緩存的接口
MemoryCache Interface 內(nèi)存緩存的接口
FuzzyKeyMemoryCache 模糊Key內(nèi)存緩存,一些不同的Key可能會被認(rèn)為是相同(通過equals方法)。當(dāng)你put一個(gè)值到cache里面時(shí),equals相同的Key也會被替換。這是一個(gè)內(nèi)部功能,通常我們不需要使用到它。
LimitedAgeMemoryCache 限制時(shí)間內(nèi)存緩存,如果一些對象的使用時(shí)間超過了定義的值,那么就會移除。比如最大時(shí)間設(shè)置成60s,那么如果一個(gè)對象是60s前put進(jìn)來的,那么它就會失效。
LruMemoryCache 一個(gè)強(qiáng)引用的圖片,限制圖片使用數(shù)量的內(nèi)存緩存,每個(gè)被訪問的圖片都會移動(dòng)到隊(duì)列的頭部。當(dāng)一個(gè)圖片添加到一個(gè)滿的隊(duì)列的時(shí)候,隊(duì)尾的圖片將會被驅(qū)逐而被垃圾回收器回收。
BaseMemoryCache 抽象類,基本的圖片緩存管理,提供了圖片存儲(強(qiáng)引用或者弱引用),具體的可以看下面的MemoryCache實(shí)現(xiàn)。
WeakMemoryCache 繼承自BaseMemoryCache,弱引用內(nèi)存緩存,弱引用,當(dāng)內(nèi)存不足的時(shí)候,會被系統(tǒng)回收。
LimitedMemoryCache 抽象類,繼承自BaseMemoryCache,限制內(nèi)存的內(nèi)存管理抽象類,包括下面4個(gè)具體實(shí)現(xiàn)。提供圖片對象存儲,存儲的所有的圖片大小將不會超過大小限制。這個(gè)緩存提供了強(qiáng)引用和弱引用存儲了圖片,強(qiáng)引用用來限制圖片大小,弱引用用來其他緩存的圖片。
FIFOLimitedMemoryCache 繼承自LimitedMemoryCache,存儲的圖片大小不小超過限定的大小。當(dāng)緩存圖片到達(dá)指定大小時(shí),會按照FIFO(先進(jìn)先出)的原則清理圖片。
LRULimitedMemoryCache 繼承自LimitedMemoryCache,存儲的圖片大小不小超過限定的大小。當(dāng)緩存圖片到達(dá)指定大小時(shí),最早使用的圖片會從緩存中刪除。
LargestLimitedMemoryCache 繼承自LimitedMemoryCache,存儲的圖片大小不小超過限定的大小。當(dāng)緩存圖片到達(dá)指定大小時(shí),最大的圖片會從緩存中刪除。
UsingFreqLimitedMemoryCache 繼承自LimitedMemoryCache,存儲的圖片大小不小超過限定的大小。當(dāng)緩存圖片到達(dá)指定大小時(shí),最少使用頻率的圖片會從緩存中刪除。

下面,我們會具體看各個(gè)MemoryCache的實(shí)現(xiàn)

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

實(shí)現(xiàn)的方法很簡單,在put的時(shí)候,用一個(gè)Map記錄了存入的時(shí)間,get的時(shí)候,判斷時(shí)間是否大于maxAge,如果是,那么就刪掉這個(gè)緩存。

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

實(shí)現(xiàn)的原理是利用LinkedHashMap,先存入的key在遍歷的時(shí)候在鏈表的頭部,所以在put值的時(shí)候,從頭開始遍歷HashMap,從頭刪除圖片緩存,直到內(nèi)存的大小在指定大小以內(nèi)。

另外為了防止多線程的問題,put和trimToSize刪除圖片的時(shí)候做了synchronized同步。

這個(gè)也是ImageLoader使用比較多的一種內(nèi)存管理方法。

六. WeakMemoryCache

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

實(shí)現(xiàn)方法很簡單,用一個(gè)弱引用就可以解決,用弱引用包裹了圖片。

七. FIFOLimitedMemoryCache

FIFOLimitedMemoryCache繼承自抽象類LimitedMemoryCache,所以看算法實(shí)現(xiàn)前還要看LimitedMemoryCache代碼。

抽象類LimitedMemoryCache和其實(shí)現(xiàn)子類的關(guān)系是LimitedMemoryCache put的時(shí)候限定超過大小時(shí)調(diào)用removeNext刪除圖片緩存;

但是具體怎么刪,刪除什么圖片緩存,就由子類實(shí)現(xiàn)。

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實(shí)現(xiàn),直接從頭開始移除。

八. 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;
   }
}

使用一個(gè) LinkedHashMap lruCache來保存圖片的put順序,需要移除的時(shí)候(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;
   }
}

使用一個(gè)HashMap usingCounts來存儲bitmap的使用次數(shù),每次get的時(shí)候都會讓使用次數(shù)+1,那需要移除的時(shí)候圖片緩存的時(shí)候,遍歷usingCounts,找到使用次數(shù)最少的圖片,刪除之。

不過這種遍歷的算法感覺效率不高,慎用!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI