算法日记——LRU和LFU的O(1)实现

LRU和LFU都可以用作缓存置换算法

LRU

LRU是Least Recently Used的缩写,即最近最久未使用
主要有两种实现方式

继承LinkedHashMap

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int MAX_CACHE_SIZE;

    public LRUCache(int cacheSize) {
//          LinkedHashMap的一个构造函数,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面
//          public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
//            super(initialCapacity, loadFactor);
//            this.accessOrder = accessOrder;
//        }
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        MAX_CACHE_SIZE = cacheSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_CACHE_SIZE;
    }
}

只需重写removeEldestEntry方法,设置删除最老元素的条件即可以

双向链表加HashMap

public class LRUCache1 {

    private final int MAX_CACHE_SIZE;
    //链表头
    private Entry first;
    //链表尾,储存最旧的元素
    private Entry last;
    //键值对
    private HashMap> hashMap;

    public LRUCache1(int cacheSize) {
        MAX_CACHE_SIZE = cacheSize;
        hashMap = new HashMap>();
    }

    //基础方法put
    public void put(K key, V value) {
        Entry entry = getEntry(key);
        if (entry == null) {
            if (hashMap.size() >= MAX_CACHE_SIZE) {
                //删除hashmap中的last元素
                hashMap.remove(last.key);
                //删除双向链表中的last节点
                removeLast();
            }
            entry = new Entry();
            entry.key = key;
        }
        entry.value = value;
        //移到链表头部
        moveToFirst(entry);
        hashMap.put(key, entry);
    }

    //基础方法get
    public V get(K key) {
        Entry entry = getEntry(key);
        if (entry == null) return null;
        moveToFirst(entry);
        return entry.value;
    }

    //将entry移到头部
    private void moveToFirst(Entry entry) {

        if (entry == first) return;
        if (entry.pre != null) entry.pre.next = entry.next;
        if (entry.next != null) entry.next.pre = entry.pre;
        if (entry == last) last = last.pre;

        if (first == null || last == null) {
            first = last = entry;
            return;
        }

        entry.next = first;
        first.pre = entry;
        first = entry;
        entry.pre = null;
    }

    //删除最老的元素
    private void removeLast() {
        if (last != null) {
            last = last.pre;
            if (last == null) first = null;
            else last.next = null;
        }
    }


    private Entry getEntry(K key) {
        return hashMap.get(key);
    }


    //双向链表
    class Entry {
        public Entry pre;
        public Entry next;
        public K key;
        public V value;
    }
}

LFU

LFU是Least Frequently Used的缩写,使用频率最少的

两个HashMap加双向链表嵌套LinkedHashSet

public class LFUCache2 {
    //链表头部
    private Node head = null;
    private int cap = 0;
    //存储数据的
    private HashMap valueHash = null;
    //key所属Node
    private HashMap nodeHash = null;

    public LFUCache2(int capacity) {
        this.cap = capacity;
        valueHash = new HashMap();
        nodeHash = new HashMap();
    }

    //基础方法get
    public Object get(K key) {
        if (valueHash.containsKey(key)) {
            increaseCount(key);
            return valueHash.get(key);
        }
        return -1;
    }

    //基础方法set
    public void set(K key, V value) {
        if ( cap == 0 ) return;
        //如果存在当前key则覆盖,并刷新key的次序
        if (valueHash.containsKey(key)) {
            valueHash.put(key, value);
            Node node = nodeHash.get(key);
            node.keys.remove(key);
            node.keys.add(key);
        } else {
            if (valueHash.size() < cap) {
                valueHash.put(key, value);
            } else {
                //如果超出容量则要移除最旧的
                removeOld();
                valueHash.put(key, value);
            }
            //因为次数为1那肯定是添加的headNode
            addToHead(key);
        }
        increaseCount(key);
    }

    //新添加值时添加到haed
    private void addToHead(K key) {
        //添加第一个值时会进入第一个if,其他情况head均不为空且次数不为0
        if (head == null) {
            head = new Node(0);
            head.keys.add(key);
        } else if (head.count > 0) {
            Node node = new Node(0);
            node.keys.add(key);
            node.next = head;
            head.prev = node;
            head = node;
        } else {
            //按道理不会走进这个else,因为插入第一个值后head均不为空且次数不为0
            head.keys.add(key);
        }
        nodeHash.put(key, head);
    }

    //增加次数
    private void increaseCount(K key) {
        //根据key获得所属Node
        Node node = nodeHash.get(key);
        //因为次数要加一,所以要从Node的LinkHashSet移除掉
        node.keys.remove(key);
        //没有key次数比当前Node大的Node则创建一个,并在添加到新Node的LinkHashSet
        if (node.next == null) {
            node.next = new Node(node.count+1);
            node.next.prev = node;
            node.next.keys.add(key);
        } else if (node.next.count == node.count+1) {
            node.next.keys.add(key);
        } else {
            //如果下一个Node的次数不是当前key次数+1,则新建一个并插入到node后面
            Node tmp = new Node(node.count+1);
            tmp.keys.add(key);
            tmp.prev = node;
            tmp.next = node.next;
            node.next.prev = tmp;
            node.next = tmp;
        }
        //覆盖key所属Node
        nodeHash.put(key, node.next);
        //如果没有key的次数和修改后的Node相等则移除当前Node
        if (node.keys.size() == 0) remove(node);
    }

    //删除最旧的元素
    private void removeOld() {
        if (head == null) return;
        Object old = null;
        //获取haed的第一个元素
        for (Object n: head.keys) {
            old = n;
            break;
        }
        //从haed的中的LinkHashSet移除掉
        head.keys.remove(old);
        //如果不存在当前次数的key则把head也移除掉
        if (head.keys.size() == 0) remove(head);
        //移除掉key的node的对应关系
        nodeHash.remove(old);
        //键值对移除掉当前key
        valueHash.remove(old);
    }

    //双向链表的删除
    private void remove(Node node) {
        if (node.prev == null) {
            head = node.next;
        } else {
            node.prev.next = node.next;
        }
        if (node.next != null) {
            node.next.prev = node.prev;
        }
    }

    //双向链表节点,装载次数和对应次数的keys的LinkedHashSet
    class Node {
        public int count = 0;
        public LinkedHashSet keys = null;
        public Node prev = null, next = null;

        public Node(int count) {
            this.count = count;
            keys = new LinkedHashSet();
            prev = next = null;
        }
    }
}

你可能感兴趣的:(算法)