【源碼】LruCache源碼剖析,lrucache源碼剖析

來源:互聯網
上載者:User

【源碼】LruCache源碼剖析,lrucache源碼剖析
上一篇分析了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方法,用於計算每個條目的大小。





<<STL源碼剖析>> << C++標準程式庫>>

侯捷把STL的學習比喻為三個境界:
第一境界:熟用STL
第二境界:瞭解泛型技術的內涵與STL的學理乃至實作
第三境界:擴充STL

這三個境界都可以使用 << C++標準程式庫>>,但是第三境界推薦使用<<STL源碼剖析>>
參考資料:<< C++標準程式庫>>中文版 侯捷 孟岩 譯
 
初學者可以看得懂《STL源碼剖析》?

《STL源碼剖析》不是講怎麼樣使用STL和STL技巧的,是關於STL核心代碼的剖析,是面向有豐富經驗的STL程式員來補充和更好的理解STL底層核心機制,初學者看這本書的話基本上是一頭霧水,建議先從基礎學起,C++標準程式庫 和C++stl是比較好的入門且使用的書籍,以後有了一定的STL經驗,再去研究STL源碼剖析,相信那時候你就會有了另一番對STL的領悟。
 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.