0
点赞
收藏
分享

微信扫一扫

LRU Java两种简单实现

田妞的读书笔记 2022-03-15 阅读 174

LRU Java两种简单实现

​ LRU即Least Recently Used ,最近最少使用的缓存替换规则,此缓存策略关注于程序访问存储介质两个性质之一:时间局部性,最近访问到的内容,接下来大概率会继续访问到。LRU的实现不难,且在java中还有现成的代码可以使用以及学习。下面给出两种LRU的实现:

1.持有或继承LinkedHashMap

​ LinkedHashMap在HashMap平均O(1)的访问基础上,存储的节点额外增加了前后指向关系:

    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

注:HashMap中的冲突链表转化为红黑树后,红黑树的node是继承了Entry的,所以也具备该前后指向关系

​ LinkedHashMap有head、tail成员变量,用于指向前后关系的头和尾。在LinkedHashMap中有个布尔的变量accessOrder,当为true时,此时前后的顺序为访问的时间顺序,为false时,记录的是插入的时间顺序,所以要做LRU缓存的话,需要设置为true。LinkedHashMap在插入和获取时,会调整元素的位置,调整到最新的位置上,即tail变量。这里的head和tail指向关系有点反常识,head指向的是最老的元素,tail指向的是最新的元素。

​ LinkedHashMap要实现缓存的关键点是重写removeEldestEntry方法,这是移除规则的实现方法,默认实现中是直接返回的false。比较简单的实现是判断当前存放元素的size是否达到的初始设置的容量,如果达到了就移除最久未使用的元素。

​ 完成实现如下:

public class  LRUCache_146 extends LinkedHashMap<Integer,Integer> {
    private int capacity;

    public LRUCache_146(int initialCapacity) {
        super(initialCapacity, 0.75f, true);
        this.capacity=initialCapacity;
    }
    
    public int get(int key) {
        return super.getOrDefault(key,-1);
    }

    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size()>capacity;
    }

    public void put(int key, int value) {
        super.put(key, value);
    }
}

​ 这个是继承的实现,持有一个LinkedHashMap的写法类似。

2.自行实现时间的前后关系

​ 在前面的第一种实现里,时间的顺序是由LinkedHashMap维护的,在这个实现里面,自行维护时间顺序,不过在hash映射上依然使用HashMap的能力。

​ 在实现之前,应该大致捋一下里面可能涉及到的变量、方法,有个大致的思路,写起来会清晰很多,笔者抽取的要点如下:

​ a.三个成员变量:head(指向最年轻的元素)、tail(指向最年老的元素)、capacity(存储的最大容量)

​ b.当新增元素或者获取已有元素时,元素需插入到head头,即insert2Head方法

​ c.当获取已有元素时,元素需要断开原前后指向关系,并插入到head头,即move2Head方法

​ d.当容量满了时,需要移除最老的元素,即removeLastUsed方法

​ 当然存储的元素entry得有前后的指向关系,即pre和next属性。

完成实现如下:

/**
 * 最近最少使用的缓存
 */
public class LRUCache {

    Map<Integer, Entry> cache;
    Entry head, tail;
    int capacity;

    public LRUCache(int capcity) {
        this.capacity = capcity;
        cache = new HashMap<>();
    }

    public int get(int key) {
        Entry entry = cache.get(key);
        if (entry == null) {
            return -1;
        }
        //调整位置,移动到头部
        move2Head(entry);
        return entry.val;
    }

    public void put(int key, int value) {
        Entry entry = cache.get(key);
        if (entry != null) {
            //修改val并调整位置到头部
            entry.val = value;
            move2Head(entry);
        } else {
            //超过容量,移除一个最近最久未使用的元素
            if (cache.size() == capacity) {
                removeLastestUsed();
            }
            entry = new Entry(key, value);
            cache.put(key, entry);
            insert2Head(entry);
        }
    }

    /**
     * 移除最近最久未使用的元素
     */
    private void removeLastUsed() {
        if (tail == null) {
            return;
        }
        cache.remove(tail.key);
        if (tail == head) {
            tail = null;
            head = null;
            return;
        }
        Entry pre = tail.pre;
        pre.next = null;
        tail.pre = null;
        tail = pre;
    }

    /**
     * 将元素移动到头部,只对已存在的entry操作
     *
     * @param entry
     */
    private void move2Head(Entry entry) {
        Entry pre = entry.pre;
        Entry next = entry.next;
        if (pre == null) {
            //说明是head节点,不用继续操作
            return;
        }
        if (next == null) {
            //说明是尾节点,需要更改tail的指向
            pre.next = null;
            entry.pre = null;
            tail = pre;
            insert2Head(entry);
            return;
        }
        pre.next = next;
        next.pre = pre;
        insert2Head(entry);
    }

    private void insert2Head(Entry entry) {
        if (tail == null && head == null) {
            head = entry;
            tail = entry;
            return;
        }
        Entry head = this.head;
        entry.next = head;
        entry.pre = null;
        head.pre = entry;
        this.head = entry;
    }


    private static class Entry {
        int key;
        int val;
        Entry pre;
        Entry next;

        public Entry(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
}

其实更狠的是,Hash映射也自己实现,这里先留一个坑位,待后面把Hash映射实现了,再把链接放在这里。

举报

相关推荐

0 条评论