在LinkedHashMap中,我們知道,LinkedHashMap為我們實現特定替換策略的Map Cache預留了介面,即以如下形式重寫removeEldestEntry函數:
private static final int MAX_ENTRIES = 100; protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_ENTRIES; }
但是LinkedHashMap有一點不足在於其實現過程中沒有考慮過並發訪問的問題,即在多線程環境下對LinkedHashMap進行訪問並不安全。
1.Andriod LruCache概述 Andriod開發這網站上給出如下描述:
A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection. If your cached values hold resources that need to be explicitly released, override entryRemoved(boolean, K, V, V). If a cache miss should be computed on demand for the corresponding keys, override create(K). This simplifies the calling code, allowing it to assume a value will always be returned, even when there's a cache miss. By default, the cache size is measured in the number of entries. Override sizeOf(K, V) to size the cache in different units. |
可以看出在Andriod LruCache中實現中還是給開發這留出了幾個靈活的擴充介面,包括 entryRemoved(boolean, K, V, V)用於對特定的Cache元素進行記憶體空間釋放操作 , create(K) 用於在訪問失效時,為特定的key產生索引值的操作,當然這兩個函數在源碼實現中都是空。
2.底層資料結構支援 我最初以為Andriod LruCache的實現是繼承了LinkedHashMap,並重寫了removeEldestEntry函數,但是一看源碼完全不是這麼回事,所以首先看一下LruCache的內部資料域以及其建構函式:
private final LinkedHashMap<K, V> map; //LinkedHashMap作為一個成員變數操作/** Size of this cache in units. Not necessarily the number of elements. */private int size;private int maxSize; //Cache元素的個數上限private int putCount;private int createCount;private int evictionCount;private int hitCount;private int missCount;/** * @param maxSize for caches that do not override {@link #sizeOf}, this is * the maximum number of entries in the cache. For all other caches, * this is the maximum sum of the sizes of the entries in this cache. */public LruCache(int maxSize) {if (maxSize <= 0) { //建構函式,初始化LinkedHashMapthrow new IllegalArgumentException("maxSize <= 0");}this.maxSize = maxSize;this.map = new LinkedHashMap<K, V>(0, 0.75f, true); //注意參數,true代表這AccessOrder}
到這裡你會想到,當我們對LruCache進行操作時,都是對其內部封裝的LinkedHashMap進行操作。
3.put put函數內部還是調用了LinkedHashMap的put操作,但是在操作過程中進行了加鎖操作,保證同一時刻只能有一個線程進行資料插入操作。
/** * Caches {@code value} for {@code key}. The value is moved to the head of * the queue. * * @return the previous value mapped by {@code key}. */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); //調用LinkedHashMap的put操作if (previous != null) {size -= safeSizeOf(key, previous);}}if (previous != null) {entryRemoved(false, key, previous, value);}trimToSize(maxSize); //緩衝容量檢測,以保證快取資料量不超過最大容量return previous;}
4.get
/** * Returns the value for {@code key} if it exists in the cache or can be * created by {@code #create}. If a value was returned, it is moved to the * head of the queue. This returns null if a value is not cached and cannot * be created. */public final V get(K key) {if (key == null) {throw new NullPointerException("key == null");}V mapValue;synchronized (this) { //加鎖的get操作mapValue = map.get(key); //調用LinkedHashMap的get操作if (mapValue != null) {hitCount++;return mapValue;}missCount++;} /* * Attempt to create a value. This may take a long time, and the map * may be different when create() returns. If a conflicting value was * added to the map while create() was working, we leave that value in * the map and release the created value. */V createdValue = create(key); //對訪問失效的處理,英文注釋裡說的很清楚,就在於你去怎麼處理了if (createdValue == null) {return null; //建立不成功直接返回就可以}synchronized (this) { //加鎖處理,保證第一個被建立的value被緩衝,從而保證了快取資料的一致性createCount++; mapValue = map.put(key, createdValue); //當前線程建立成功,進行put操作 if (mapValue != null) {// There was a conflict so undo that last putmap.put(key, mapValue); //發現之前已經有線程完成建立操作了,保持原有的緩衝value操作} else {size += safeSizeOf(key, createdValue);}}if (mapValue != null) { //執行到這裡,證明一定發生了替換操作,並且當前線程是後替換操作entryRemoved(false, key, createdValue, mapValue); //釋放當前線程建立的valuereturn mapValue;} else {trimToSize(maxSize); //到這裡證明當前線程完成了建立操作,並且當前線程建立的value被緩衝return createdValue; //所以需要進行緩衝容量檢測}}
5.緩衝容量控制trimToSize
/** * @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) {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);size -= safeSizeOf(key, value);evictionCount++;}entryRemoved(true, key, value, null); //本線程負責釋放已存在元素的空間}}
5.小結 Android的LruCache的實現是對LinkedHashMap進行了一層封裝,並在其中加入了支援多線程訪問的線程鎖操作,因此LruCache時支援並發訪問的。