jodd.cache源碼閱讀筆記

簡介

jodd.cache包中有FIFOLRULFU等幾種緩存置換算法的實現(xiàn)

FIFO -- 先進先出

FIFO
  1. 新訪問的數(shù)據(jù)插入FIFO隊列尾部,數(shù)據(jù)在FIFO隊列中順序移動
  2. 淘汰FIFO隊列頭部的數(shù)據(jù)

LRU -- 最久未使用

LRU
  1. 新數(shù)據(jù)插入到鏈表頭部
  2. 每當緩存命中(即緩存數(shù)據(jù)被訪問),則將數(shù)據(jù)移到鏈表頭部
  3. 當鏈表滿的時候,將鏈表尾部的數(shù)據(jù)丟棄

LFU -- 最近最少使用

LFU
  1. 新加入數(shù)據(jù)插入到隊列尾部(因為引用計數(shù)為1)
  2. 隊列中的數(shù)據(jù)被訪問后,引用計數(shù)增加,隊列重新排序
  3. 當需要淘汰數(shù)據(jù)時,將已經(jīng)排序的列表最后的數(shù)據(jù)塊刪除

代碼實現(xiàn)

繼承關系

UML
  • Cache:緩存接口
  • AbstractCacheMap:抽象類,實現(xiàn)一些公共的邏輯(讀寫鎖等)
  • LRUCache:LRU替換算法實現(xiàn)
  • LFUCache:LFU替換算法實現(xiàn)
  • FIFOCache:FIFO替換算法實現(xiàn)
  • TimedCache:無容量限制緩存,但可以通過定時任務清除超時的對象

Cache

public interface Cache<K, V> {

    /**
     * 返回緩存容器的大小限制,如果為0則為不限制
     */
    int limit();

    /**
     * 返回超時時間,如果為0則沒有超時
     */
    long timeout();

    /**
     * 使用默認超時時間(0)添加緩存對象
     */
    void put(K key, V object);

    /**
     * 使用自定的超時時間添加緩存對象
     * 如果緩存容器已經(jīng)滿將調用purne()方法來獲得空間
     */
    void put(K key, V object, long timeout);

    /**
     * 根據(jù)鍵從容器中取得緩存對象
     * 如果該對象已被清除將返回null
     */
    V get(K key);

    /**
     * 從緩存容器中移除對象并返回移除的對象的個數(shù)
     */
    int prune();

    /**
     * 返回緩存容器是否已滿,當且僅當容器容積有限制的情況下
     */
    boolean isFull();

    /**
     * 從容器中移除一個對象
     */
    void remove(K key);

    /**
     * 清空容器
     */
    void clear();

    /**
     * 返回當前緩存的對象的數(shù)量
     */
    int size();

    /**
     * 返回緩存容器是否為空
     */
    boolean isEmpty();

    /**
     * 返回一個包含容器中緩存對象的Map對象
     * 期間會鎖定容器
     */
    Map<K, V> snapshot();
}

AbstractCacheMap

public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
    // Cache Entry
    class CacheObject<K2, V2> {
        CacheObject(K2 key, V2 object, long ttl) {
            this.key = key;
            this.cachedObject = object;
            this.ttl = ttl;
            this.lastAccess = System.currentTimeMillis();
        }

        final K2 key;
        final V2 cachedObject;
        long lastAccess;        // 最后訪問時間,供LRU實現(xiàn)使用
        long accessCount;        // 訪問計數(shù),供LFU實現(xiàn)使用
        long ttl;                // 對象超時時間, 0 = 沒有超時

        // 是否過期
        boolean isExpired() {
            if (ttl == 0) {
                return false;
            }
            return lastAccess + ttl < System.currentTimeMillis();
        }

        // 獲得緩存對象并刷新訪問時間
        V2 getObject() {
            lastAccess = System.currentTimeMillis();
            accessCount++;
            return cachedObject;
        }
    }

    // 用于存放緩存的Map,在實現(xiàn)類中具體實例化
    protected Map<K, CacheObject<K, V>> cacheMap;
    // 讀寫鎖
    private final StampedLock lock = new StampedLock();

    // ---------------------------------------------------------------- properties
    // 緩存大小
    protected int cacheSize;      // max cache size, 0 = no limit

    public int limit() {
        return cacheSize;
    }

    // 緩存容器默認超時時間,默認0
    protected long timeout;     // default timeout, 0 = no timeout

    /**
     * Returns default cache timeout or <code>0</code> if it is not set.
     * Timeout can be set individually for each object.
     */
    public long timeout() {
        return timeout;
    }

    // 是否有緩存對象曾自定義超時時間
    protected boolean existCustomTimeout;

    // 緩存替換時是否需要對對象的存活狀態(tài)進行判斷
    protected boolean isPruneExpiredActive() {
        return (timeout != 0) || existCustomTimeout;
    }


    // ---------------------------------------------------------------- put


    public void put(K key, V object) {
        put(key, object, timeout);
    }


    public void put(K key, V object, long timeout) {
        final long stamp = lock.writeLock();

        try {
            CacheObject<K, V> co = new CacheObject<>(key, object, timeout);
            // 緩存對象自定義過超時時間
            if (timeout != 0) {
                existCustomTimeout = true;
            }
            // 判斷是否達到緩存容器上限,是的話觸發(fā)替換(達到最大容量,但鍵值已存在不觸發(fā),替換為新對象)
            if (isReallyFull(key)) {
                pruneCache();
            }
            cacheMap.put(key, co);
        } finally {
            lock.unlockWrite(stamp);
        }
    }


    // ---------------------------------------------------------------- get

    // 命中次數(shù)
    protected int hitCount;
    // 非命中次數(shù)
    protected int missCount;

    /**
     * Returns hit count.
     */
    public int getHitCount() {
        return hitCount;
    }

    /**
     * Returns miss count.
     */
    public int getMissCount() {
        return missCount;
    }

    public V get(K key) {
        long stamp = lock.readLock();

        try {
            CacheObject<K, V> co = cacheMap.get(key);
            if (co == null) {
                missCount++;
                return null;
            }
            // 判斷是否過期
            if (co.isExpired()) {
                // 嘗試獲得寫鎖,獲取失敗則釋放讀鎖,手動獲得讀鎖
                final long newStamp = lock.tryConvertToWriteLock(stamp);

                if (newStamp != 0L) {
                    stamp = newStamp;
                    // lock is upgraded to write lock
                } else {
                    // manually upgrade lock to write lock
                    lock.unlockRead(stamp);
                    stamp = lock.writeLock();
                }
                // 移除過期對象
                CacheObject<K, V> removedCo = cacheMap.remove(key);
                // 觸發(fā)移除后的鉤子函數(shù)(Files Cache用的)
                if (removedCo != null) {
                    onRemove(removedCo.key, removedCo.cachedObject);
                }

                missCount++;
                return null;
            }
            // 緩存命中,返回對象
            hitCount++;
            return co.getObject();
        } finally {
            lock.unlock(stamp);
        }
    }

    // ---------------------------------------------------------------- prune

    // 緩存修剪具體實現(xiàn)
    protected abstract int pruneCache();

    public final int prune() {
        final long stamp = lock.writeLock();
        try {
            return pruneCache();
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    // ---------------------------------------------------------------- common
    // 以下方法基本為加鎖訪問Map的對應方法
    public boolean isFull() {
        if (cacheSize == 0) {
            return false;
        }
        return cacheMap.size() >= cacheSize;
    }

    protected boolean isReallyFull(K key) {
        if (cacheSize == 0) {
            return false;
        }
        if (cacheMap.size() >= cacheSize) {
            return !cacheMap.containsKey(key);
        } else {
            return false;
        }
    }

    public void remove(K key) {
        final long stamp = lock.writeLock();
        try {
            CacheObject<K, V> co = cacheMap.remove(key);
            if (co != null) {
                onRemove(co.key, co.cachedObject);
            }
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    public void clear() {
        final long stamp = lock.writeLock();
        try {
            cacheMap.clear();
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    public int size() {
        return cacheMap.size();
    }

    public boolean isEmpty() {
        return size() == 0;
    }

    @Override
    // 返回一個cache map的拷貝
    public Map<K, V> snapshot() {
        final long stamp = lock.writeLock();
        try {
            Map<K, V> map = new HashMap<>(cacheMap.size());
            cacheMap.forEach((key, cacheValue) -> map.put(key, cacheValue.getObject()));
            return map;
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    // ---------------------------------------------------------------- protected

    /**
     * Callback called on item removal. The cache is still locked.
     */
    protected void onRemove(K key, V cachedObject) {
    }

}

LRUCache

public class LRUCache<K, V> extends AbstractCacheMap<K, V> {

    public LRUCache(int cacheSize) {
        this(cacheSize, 0);
    }

    /**
     * Creates a new LRU cache.
     */
    public LRUCache(int cacheSize, long timeout) {
        this.cacheSize = cacheSize;
        this.timeout = timeout;
        // cacheMap使用LinkedHashMap實現(xiàn)
        // 不自動擴容,按訪問順序排序,達到容量后移除末尾元素
        cacheMap = new LinkedHashMap<K, CacheObject<K, V>>(cacheSize + 1, 1.0f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return LRUCache.this.removeEldestEntry(size());
            }
        };
    }

    /**
     * 用來判斷是否需要移除尾部元素
     */
    protected boolean removeEldestEntry(int currentSize) {
        if (cacheSize == 0) {
            return false;
        }
        return currentSize > cacheSize;
    }

    // ---------------------------------------------------------------- prune

    // 遍歷緩存map,移除超時對象,返回超時對象計數(shù)
    // 如果沒有定義超時,直接返回0
    @Override
    protected int pruneCache() {
        if (!isPruneExpiredActive()) {
            return 0;
        }
        int count = 0;
        Iterator<CacheObject<K, V>> values = cacheMap.values().iterator();
        while (values.hasNext()) {
            CacheObject<K, V> co = values.next();
            if (co.isExpired()) {
                values.remove();
                onRemove(co.key, co.cachedObject);
                count++;
            }
        }
        return count;
    }
}

LFUCache

public class LFUCache<K, V> extends AbstractCacheMap<K, V> {

    public LFUCache(int maxSize) {
        this(maxSize, 0);
    }

    public LFUCache(int maxSize, long timeout) {
        this.cacheSize = maxSize;
        this.timeout = timeout;
        cacheMap = new HashMap<>(maxSize + 1);
    }

    // ---------------------------------------------------------------- prune

    /**
     * Prunes expired and, if cache is still full, the LFU element(s) from the cache.
     * On LFU removal, access count is normalized to value which had removed object.
     * Returns the number of removed objects.
     */
    @Override
    protected int pruneCache() {
        int count = 0;
        CacheObject<K, V> comin = null;

        // remove expired items and find cached object with minimal access count
        Iterator<CacheObject<K, V>> values = cacheMap.values().iterator();
        // 移除超時對象,并獲得存活對象中訪問計數(shù)中最小的對象==>comin
        while (values.hasNext()) {
            CacheObject<K, V> co = values.next();
            if (co.isExpired()) {
                values.remove();
                onRemove(co.key, co.cachedObject);
                count++;
                continue;
            }

            if (comin == null) {
                comin = co;
            } else {
                if (co.accessCount < comin.accessCount) {
                    comin = co;
                }
            }
        }

        // 容器沒滿直接返回
        if (!isFull()) {
            return count;
        }

        // 遍歷cache map,移除訪問計數(shù)值等于或小于最小計數(shù)的對象
        // 以最小計數(shù)為原點,重新規(guī)范計數(shù)器
        if (comin != null) {
            long minAccessCount = comin.accessCount;

            values = cacheMap.values().iterator();
            while (values.hasNext()) {
                CacheObject<K, V> co = values.next();
                co.accessCount -= minAccessCount;
                if (co.accessCount <= 0) {
                    values.remove();
                    onRemove(co.key, co.cachedObject);
                    count++;
                }
            }
        }
        return count;
    }
}

FIFOCache

public class FIFOCache<K, V> extends AbstractCacheMap<K, V> {
    
        public FIFOCache(int cacheSize) {
            this(cacheSize, 0);
        }
    
        /**
         * Creates a new LRU cache.
         */
        public FIFOCache(int cacheSize, long timeout) {
            this.cacheSize = cacheSize;
            this.timeout = timeout;
            // 依舊使用LinkedHashMap作為存儲,不自動擴容,按插入順序排序
            cacheMap = new LinkedHashMap<>(cacheSize + 1, 1.0f, false);
        }
    
    
        // ---------------------------------------------------------------- prune
    
        // 移除過期元素,如果空間還是不足,移除最早插入的元素(鏈表尾部)
        @Override
        protected int pruneCache() {
            int count = 0;
            CacheObject<K,V> first = null;
            Iterator<CacheObject<K,V>> values = cacheMap.values().iterator();
            while (values.hasNext()) {
                CacheObject<K,V> co = values.next();
                if (co.isExpired()) {
                    values.remove();
                    count++;
                }
                if (first == null) {
                    first = co;
                }
            }
            if (isFull()) {
                if (first != null) {
                    cacheMap.remove(first.key);
                    count++;
                }
            }
            return count;
        }
    }
}

TimedCache

public class TimedCache<K, V> extends AbstractCacheMap<K, V> {
        // TimedCache沒有容量限制,但必須制定緩存對象的超時時間
        // 定時任務可以根據(jù)元素是否超時移除元素
        public TimedCache(long timeout) {
            this.cacheSize = 0;
            this.timeout = timeout;
            cacheMap = new HashMap<>();
        }
    
        // ---------------------------------------------------------------- prune
    
        /**
         * 遍歷Map,移除超時元素
         */
        @Override
        protected int pruneCache() {
            int count = 0;
            Iterator<CacheObject<K,V>> values = cacheMap.values().iterator();
            while (values.hasNext()) {
                CacheObject co = values.next();
                if (co.isExpired()) {
                    values.remove();
                    count++;
                }
            }
            return count;
        }
    
    
        // ---------------------------------------------------------------- auto prune
    
        protected Timer pruneTimer;
    
        /**
         * 開啟定時清理
         */
        public void schedulePrune(long delay) {
            if (pruneTimer != null) {
                pruneTimer.cancel();
            }
            pruneTimer = new Timer();
            pruneTimer.schedule(
                    new TimerTask() {
                        @Override
                        public void run() {
                            prune();
                        }
                    }, delay, delay
            );
        }
    
        /**
         * 取消定時清理
         */
        public void cancelPruneSchedule() {
            if (pruneTimer != null) {
                pruneTimer.cancel();
                pruneTimer = null;
            }
        }    
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1. LRU 1.1.原理 LRU(Leastrecentlyused,最近最少使用)算法根據(jù)數(shù)據(jù)的歷史訪問記錄來...
    安易學車閱讀 2,561評論 0 23
  • 1. LRU 1.1. 原理LRU(Least recently used,最近最少使用)算法根據(jù)數(shù)據(jù)的歷史訪問記...
    AKyS佐毅閱讀 2,198評論 0 3
  • Android緩存淺析 By吳思博 1、引言 2、常見的幾種緩存算法 3、Android緩存的機制 4、LruCa...
    吳小博Toby閱讀 2,930評論 1 5
  • 緩存淘汰算法--LRU算法 1. LRU 1.1 原理 LRU(Least recently used,)算法根據(jù)...
    白公子是貓奴閱讀 484評論 0 0
  • LRU原理 LRU(Least recently used,最近最少使用)算法根據(jù)數(shù)據(jù)的歷史訪問記錄來進行淘汰數(shù)據(jù)...
    jiangmo閱讀 60,287評論 3 30