1. 程式人生 > >【源代碼】LruCache源代碼剖析

【源代碼】LruCache源代碼剖析

name 丟失 next() 兩個 sna eight get方法 util rem

上一篇分析了LinkedHashMap源代碼,這個Map集合除了擁有HashMap的大部分特性之外。還擁有鏈表的特點,即能夠保持遍歷順序與插入順序一致。

另外。當我們將accessOrder設置為true時。能夠使遍歷順序和訪問順序一致,其內部雙向鏈表將會依照最近最少訪問到最近最多訪問的順序排列Entry對象,這能夠用來做緩存。


這篇文章分析的LruCache並非jdk中的類,而是來自安卓,熟悉安卓內存緩存的必定對這個類不陌生。

LruCache內部維護的就是一個LinkedHashMap。 以下開始分析LruCache。 註:以下LruCache源代碼來自support.v4包。

首先是這個類的成員變量:

 private final LinkedHashMap<K, V> map;
    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;//當前大小
    private int maxSize;//最大容量
    private int putCount;//put次數
    private int createCount;//create次數
    private int evictionCount;//回收次數
    private int hitCount;//命中次數
    private int missCount;//丟失次數
LinkedHashMap的初始化放在構造器中:
public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

這裏將LinkedHashMap的accessOrder設置為true。

接下來看兩個最重要的方法,put和get。

首先是put方法:

public final V put(K key, V value) {
        if (key == null || value == null) {//鍵值不同意為空
            throw new NullPointerException("key == null || value == null");
        }
        V previous;
        synchronized (this) {//線程安全
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {//之前已經插入過同樣的key
                size -= safeSizeOf(key, previous);//那麽減去該entry的容量,由於發生覆蓋
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, value);//這種方法默認空實現
        }
        trimToSize(maxSize);//若容量超過maxsize,將會刪除近期非常少訪問的entry
        return previous;
    }
put方法無非就是調用LinkedHashMap的put方法。可是這裏在調用LinkedHashMap的put方法之前,推斷了key和value是否為空,也就是說LruCache不同意空鍵值

除此之外,put操作被加鎖了,所以是線程安全的! 既然是緩存,那麽必定可以動態刪除一些不經常使用的鍵值對,這個工作是由trimToSize方法完畢的:

 public void trimToSize(int maxSize) {
        while (true) {//不斷刪除linkedHashMap頭部entry,也就是近期最少訪問的條目。直到size小於最大容量
            K key;
            V 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<K, V> toEvict = map.entrySet().iterator().next();//指向鏈表頭
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);//刪除最少訪問的entry
                size -= safeSizeOf(key, value);
                evictionCount++;
            }
            entryRemoved(true, key, value, null);
        }
    }

這種方法不斷循環刪除鏈表首部元素。也就是近期最少訪問的元素,直到容量不超過預先定義的最大值為止。 註:LruCache在android.util包中也有一個LruCache類,可是我發現這個類的trimToSize方法是錯誤的:
private void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
                if (size <= maxSize) {
                    break;
                }
            
                Map.Entry<K, V> toEvict = null;
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                }
    
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }
            entryRemoved(true, key, value, null);
        }
    }
這裏的代碼將會循環刪除鏈表尾部,也就是近期訪問最多的元素,這是不對的!所以大家在做內存緩存的時候一定要註意,看trimToSize方法是否有問題。
接下來是get方法:
public final V get(K key) {
        if (key == null) {//不同意空鍵
            throw new NullPointerException("key == null");
        }
        V mapValue;
        synchronized (this) {//線程安全
            mapValue = map.get(key);//調用LinkedHashMap的get方法
            if (mapValue != null) {
                hitCount++;//命中次數加1
                return mapValue;//返回value
            }
            missCount++;//未命中
        }

        V createdValue = create(key);//默認返回為false
        if (createdValue == null) {
            return null;
        }
        synchronized (this) {
            createCount++;//假設創建成功,那麽create次數加1
            mapValue = map.put(key, createdValue);//放到哈希表中
            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }
        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

get方法即依據key在LinkedHashMap中尋找相應的value,此方法也是線程安全的。
以上就是LruCache最重要的部分,以下再看下其它方法: remove:
  public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V previous;
        synchronized (this) {
            previous = map.remove(key);//調用LinkedHashMap的remove方法
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }
        return previous;//返回value
    }

sizeof:這種方法用於計算每一個條目的大小,子類必須得復寫這個類。
 protected int sizeOf(K key, V value) {//用於計算每一個條目的大小
        return 1;
    }
snapshot方法,返回當前緩存中全部的條目集合
 public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }

總結: 1.LruCache封裝了LinkedHashMap。提供了LRU緩存的功能; 2.LruCache通過trimToSize方法自己主動刪除近期最少訪問的鍵值對。 3.LruCache不同意空鍵值; 4.LruCache線程安全。 5.繼承LruCache時,必需要復寫sizeof方法。用於計算每一個條目的大小。






【源代碼】LruCache源代碼剖析