LRU Cache java實現


要求:

  • get(key):如果key在cache中,則返回對應的value值,否則返回null
  • set(key,value):如果key不在cache中,則將該(key,value)插入cache中(注意,如果cache已滿,則必須把最近最久未使用的元素從cache中刪除);如果key在cache中,則重置value的值。
  • set和get的時間復雜度都是O(1)。

兩個map


/**
 * 思路:時間復雜度是O(1),一下子想到的是map。但是怎么進行淘汰呢?需要記錄時間,且查找的復雜度也是O(1),那也用map吧。
 * put之前,需要先查看是否包含key,如果包含,刪掉之前的數據,再放進新的數據,且把key的訪問時間更新。淘汰最早訪問數據的時候,還需要根據訪問時間查詢key。
 * 所以,需要的操作包括:根據key獲取value和access;根據access獲取key。
 */
public class MyLRUMapSample<K, V> {
        //cache的容量
        private int capacity;
        //存儲key-value/access的map
        private HashMap<K, Pair<V>> valueMap;
        //存儲access-key的map,使用SortedMap,對access進行排序
        private SortedMap<Long, K> accessKeyMap;

        public MyLRUMapSample(){}
        public MyLRUMapSample(int capacity){
            this.capacity = capacity;
            valueMap = new HashMap<K, Pair<V>>();
            accessKeyMap = new TreeMap<Long, K>();
        }

        public V get(K key){
            if(!valueMap.containsKey(key)){
                return null;
            }
            //修改access
            Long access = accessKeyMap.lastKey() + 1;
            accessKeyMap.remove(valueMap.get(key).access);
            accessKeyMap.put(access, key);
            valueMap.get(key).access = access;
            return valueMap.get(key).value;
        }

        public void put(K key, V value){
            //如果含有值,則更新
            if(valueMap.containsKey(key)){
                Long oldAccess = valueMap.get(key).access;
                accessKeyMap.remove(oldAccess);
                valueMap.remove(key);
            }

            //如果存儲已滿,則淘汰掉最早訪問的
            if(valueMap.size() >= capacity){
                Long oldAccess = accessKeyMap.firstKey();
                valueMap.remove(accessKeyMap.get(oldAccess));
                accessKeyMap.remove(oldAccess);
            }

            //添加最新的數據
            Long access = accessKeyMap.isEmpty() ? 0 : accessKeyMap.lastKey() + 1;
            valueMap.put(key, new Pair(access, value));
            accessKeyMap.put(access, key);
        }

        //訪問時間和value值
        class Pair<V>{
            public Long access;
            public V value;
            public Pair(){}
            public Pair(Long access, V value){
                this.access = access;
                this.value = value;
            }
        }

    public K getOldestKey(){
        return accessKeyMap.isEmpty() ? null : accessKeyMap.get(accessKeyMap.firstKey());
    }


    public K getLatestKey(){
        return accessKeyMap.isEmpty() ? null : accessKeyMap.get(accessKeyMap.lastKey());
    }


    public static void main(String[] args) {
        LinkedListMapLRUSample<Integer, Integer> sample = new LinkedListMapLRUSample<Integer, Integer>(2);
        Assert.check(sample.getLatestKey() == null);
        Assert.check(sample.get(2) == null);
        sample.put(2, 1);
        sample.put(2, 2);
        Assert.check(sample.get(2).intValue() == 2);
        sample.put(1, 2);
        sample.put(1, 3);
        Assert.check(sample.get(1) == 3);
        Assert.check(sample.get(2).intValue() == 2);

		sample.put(3, 3);
        Assert.check(sample.get(1) == null);
    }


}

雙向鏈表+hashMap

/**
 * 使用LinkedList存儲數據,從頭部插入,從尾部淘汰。這樣就保證了容量和淘汰規則正確性。
 * 使用hashMap,通過key找到value。
 */
public class LinkedListMapLRUSample<K,V> {

    class Node<K, V>{
        public K key;
        public V value;
        public Node preNode;
        public Node nextNode;
        public Node(){}
        public Node(K key, V value, Node preNode, Node nextNode){
            this.key = key;
            this.value = value;
            this.preNode = preNode;
            this.nextNode = nextNode;
        }
    }

    private Node<K,V> head;
    private Node<K,V> tail;
    private HashMap<K,Node<K, V>> valueMap;
    private int capacity;

    public LinkedListMapLRUSample(){}
    public LinkedListMapLRUSample(int capacity){
        this.capacity = capacity;
        valueMap = new HashMap<K,Node<K, V>>();
    }

    public void put(K key, V value){
        if(head == null){
            head = new Node(key, value, null, null);
            tail = head;
            valueMap.put(key, head);
            return;
        }

        //如果已經包含了數據,需要更新
        if(valueMap.containsKey(key)){
            valueMap.get(key).value = value;
            moveToHead(key);
            return;
        }

        //滿容量,需要淘汰掉最舊的數據
        if(valueMap.size() >= capacity){
            valueMap.remove(tail.key);
            tail = tail.preNode;
            if(tail != null){
                tail.nextNode = null;
            }
        }

        insertAsHead(key, value);

    }

    private void insertAsHead(K key, V value){
        //在頭部添加新節點
        Node target = new Node(key, value, null, head);
        if(head != null){
            head.preNode = target;
            target.nextNode = head;
        }
        valueMap.put(key, target);
        head = target;
    }

    private void moveToHead(K key){
        Node target = valueMap.get(key);
        //頭部元素
        if(target.preNode == null){
            return;
        }
        //尾部元素
        if(target.nextNode == null){
            tail = target.preNode;
            tail.nextNode = null;
        }else{
            target.preNode.nextNode = target.nextNode;
            target.nextNode.preNode = target.preNode;
        }

        target.preNode = null;
        target.nextNode = head;
        head.preNode = target;
        head = target;
    }

    public V get(K key){
        if(!valueMap.containsKey(key)){
            return null;
        }
        moveToHead(key);
        return valueMap.get(key).value;
    }

    public K getLatestKey(){
        return head == null ? null : head.key;
    }

    public K getOldestKey(){
        return tail == null ? null : tail.key;
    }

    public static void main(String[] args) {
        LinkedListMapLRUSample<Integer, Integer> sample = new LinkedListMapLRUSample<Integer, Integer>(2);
        Assert.check(sample.getLatestKey() == null);
        Assert.check(sample.get(2) == null);
        sample.put(2,1);
        sample.put(2, 2);
        Assert.check(sample.get(2).intValue() == 2);
        sample.put(1, 2);
        sample.put(1, 3);
        Assert.check(sample.get(1) == 3);
        Assert.check(sample.get(2).intValue() == 2);

        sample.put(3, 3);
        Assert.check(sample.get(1) == null);
    }
}

需要根據訪問時間或者插入時間進行排序時,考慮使用雙向鏈表。

最先插入淘汰和最少訪問淘汰

  • 最先插入淘汰,即FIFO。也就是要求set操作時,新數據放在head,get操作不需要移動。那么,只需要在get操作的時候直接返回valueMap中的數據即可。
  • 最少訪問淘汰,是按照訪問次數排序,將訪問次數最少的數據刪除。
    可以使用兩個map實現,valueMap(HashMap)和accessCountMap(SortedMap)。accessCount初始為1,get/put時,accessCount自增,並按照accessCount排序。

如果用數組和map實現,就需要accessCount自增后,進行重排序;或者在需要淘汰的時候進行重排序。

LinkedHashMap源碼解讀

LinkedHashMap使用一個雙向鏈表加一個HashMap。同時有一個成員變量accessOrder控制淘汰策略:為true 表示按照LRU方式淘汰,false表示按照FIFO方式淘汰。
另外還有一個方法控制是否刪除最舊的數據:

    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

   //put新數據的時候,檢查是否需要刪除最舊的數據
	void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex);

        // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }

使用LinkedHashMap實現LRUCache:

public class LinkedHashMapLRUSample<K, V> extends LinkedHashMap<K, V>{
    private int capacity;
    public LinkedHashMapLRUSample(){}
    public LinkedHashMapLRUSample(int capacity){
        super(16, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return this.size() > capacity;
    }

    public static void main(String[] args) {
        LinkedHashMapLRUSample<Integer, Integer> sample = new LinkedHashMapLRUSample<Integer, Integer>(2);
        Assert.check(sample.get(2) == null);
        sample.put(2,1);
        sample.put(2, 2);
        Assert.check(sample.get(2).intValue() == 2);
        sample.put(1, 3);
        Assert.check(sample.get(1) == 3);
        Assert.check(sample.get(2).intValue() == 2);

        sample.put(3, 3);
        Assert.check(sample.get(1) == null);
    }
}

參考

緩存算法和實現 : 對各種緩存算法做了說明。
LRU算法機器變種


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM