LruCache-一個工具類

單同志發表於2019-08-09

介紹

一個快取類,持有快取物件的強引用。每當使用到一個快取的value,會把它放到資料容器的頂部。當快取存滿,資料容器尾部的快取value被驅逐。

注意點

  • 如果你的快取物件需要顯示釋放,重寫entryRemoved
  • 如果讀取快取發生未命中而且需要為此次讀取生成一個物件返回,需要重寫create。這樣的話,使用者可以認為即使快取未命中,夜總會返回一個value。
  • 預設,快取的size是計算的entry的個數。你可以通過重寫sizeOf實現快取的size是每個entery對應value的大小相加。
  • LruCache不予許null作為key或者value。當出現return為null,代表沒有這個key對應的value
  • LruCache提供的操作是執行緒安全的。

閱讀

LinkedHashMap是Lrucache的核心資料容器,LruCache最近最少使用策略也是通過LinedHashMap天然實現。我們這裡不去談LinkedHashMap的儲存,只看LurCache的初始化,get/put/remove操作。

初始化

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);
    }
複製程式碼

只是初始化maxsize欄位,沒做什麼特別的事情。

get

public final V get(K key) {
        // 驗證key不能為null
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        //進行讀取動作,通過synchronized保證併發
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }
        // 未命中時,create一個value。若未重寫create,返回null
        // 這個方法可能發生併發操作。若發生併發,永遠拋棄createValue
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        // 可以發現,任何涉及對linkedhashmap進行操作的時候,
        // 需要進行synchronization處理
        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // 這裡就是上面說的,併發的時候,拋棄createValue
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }
複製程式碼

put

public final V put(K key, V value) {
        // 不支援key value 為null。
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            // 不管三七二十一先加入linkedhashmap,最後判斷是否溢位
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
        // 這裡判斷溢位,回收。
        trimToSize(maxSize);
        return previous;
    }
複製程式碼

remove

public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }

        return previous;
    }
複製程式碼

trimToSize 最少演算法在這裡被涉及到

**public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                // linkedhashmap的eldest就提供了最少使用的entry。
                Map.Entry<K, V> toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }**
複製程式碼

總結

看了這幾個操作,是否感覺空空?是的,這幾個操作也就是注意synchronization,固定的entryRmove通知,再無其他。而最少使用的實現,看LinkedHashMap

相關文章