LRU

LRU的实现

  LRU(Least Recently Used)是一种常见的页面置换算法,在计算中,所有的文件操作都要放在内存中进行,然而计算机内存大小是固定的,所以不可能把所有的文件都加载到内存,因此需要制定一种策略对加入到内存中的文件进项选择。
  LRU的设计原理:当数据在最近一段时间经常被访问,那么它在以后也会经常被访问。这就意味着,如果经常访问的数据,需要其能够快速命中,而不常访问的数据,在容量超出限制内,要将其淘汰。当数据按照如下顺序进行访问时,LRU的工作原理如下:
在这里插入图片描述
  为了实现既能够让其搜索快,又能够快速进行增删操作,这里可以选择 链表+hash表,hash表的搜索可以达到0(1)时间复杂度。hash表选用HashMap;链表,Node一个双向链表的实现。通过HashMap中key存储Node的key,value存储Node来建立Map对Node的映射关系。将HashMap看作是一张检索表,可以快速检索到需要定位的Node。大致思路:

  1. 构建双向链表节点ListNode,应包含key,value,prev,next这几个基本属性
  2. 对于Cache对象来说,我们需要规定缓存的容量,所以在初始化时,设置容量大小,然后实例化双向链表的head,tail,并让head.next->tail tail.prev->head,这样我们的双向链表构建完成
  3. 对于get操作,首先查阅hashmap,如果存在的话,直接将Node从当前位置移除,然后插入到链表的首部,在链表中实现删除直接让node的前驱节点指向后继节点。如果不存在,那么直接返回Null
  4. 对于put操作
class LRUCache {
    class Node {
        int key, value;
        Node next, pre;
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
            next = null;
            pre = null;
        }
    }
    class NodeList {
        int size;
        Node head, tail;
        public NodeList() {
            size = 0;
            head = new Node(0, 0);
            tail = new Node(0, 0);
            head.next = tail;
            tail.pre = head;
        }
        public void addFirst(Node node) {
            size ++;
            head.next.pre = node;
            node.next = head.next;
            node.pre = head;
            head.next = node;
        }
        public void remove(Node node) {
            node.pre.next = node.next;
            node.next.pre = node.pre;
            size --;
        }
        public Node removeLast() {
            if (head.next == tail) {
                return null;
            }
            Node node = tail.pre;
            remove(node);
            return node;
        }
        public int size() {
            return size;
        }
    }
    int capacity;
    Map<Integer, Node> map;
    NodeList list;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        list = new NodeList();
        map = new HashMap<>(capacity);
    }
    public int get(int key) {
        if (!map.containsKey(key)) return - 1;
        Node node = map.get(key);
        put(key, node.value);
        return node.value;
    }
    public void put(int key, int value) {
        Node node = new Node(key, value);
        if (map.containsKey(key)) {
            Node temp = map.get(key);
            map.remove(key);
            list.remove(temp);
            list.addFirst(node);
            map.put(key, node);
        } else {
            if (list.size() == capacity) {
                map.remove(list.removeLast().key);
            } 
            list.addFirst(node);
            map.put(key, node);
        }
    }
}

高吞吐、线程安全的LRU缓存

线程安全
  线程安全的思路可以通过ConcurrentHashMap上去考虑,在1.8版本之后虽然取消了段的概念,但是在实现上依然采用的是减小锁粒度思想来实现高并发,这个粒度变得更小了,为每一个桶分配了一个锁。

高效LRU
  HashMap部分确实可以采用ConcurrentHashMap的实现去达到线程安全,但是对于维持LRU的双向链表来说,对该链表上的操作性能还是比较低下的,虽然对不同段的节点进行操作可以使用不同的锁,可以实现更高的并发量,但是对于维护LRU还是需要对head和tail进行加锁,这样还是会导致线程之间的串行执行。
  可以借鉴减小锁粒度的思想来实现LRU,分段实现锁分离+每个段内维护一份退化链表,即在每一个段中各自维护相应的LRU链表,这样就可以在不同的段真正实现隔离,实现高并发。
【实现策略】:

  1. 锁分离机制。内部分成了多个segement,每个segement是独立加锁,相互不干扰
  2. 每个segement内部维护一个双向链表,每次添加,就把节点移动到退化链表头部
  3. 每次put操作,通过hash,散到每个segement中,判断segment的容量是否到达阈值。如果到达阈值,则删除退化链表中最末尾的节点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值