zoukankan      html  css  js  c++  java
  • 基于双向链表和哈希表实现LRU

    LRU

    LRU的定义
    • LRU是一种页面置换算法,即当内存中没有空闲页面但又需要内存时,操作系统会选择内存中的一个页面进行淘汰。淘汰页面的规则称为页面置换算法,同样,这种淘汰页面的规则也适用于缓存淘汰。LRU全称是最近最久未使用算法,LRU算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。根据局部性原理,当一个页面最近被访问,他很有可能再次被访问。因此LRU通常性能比较好。
    LRU实现分析
    • 我们假设将内存中所有到访的页面放置到链表中,新加入的数据尾插。内存中已经存在的页面被访问时,会将其移向链表的尾部。也就是说那些刚加入或者刚访问过的页面都会靠近链表尾部,因此最久未被使用的页面自然就在链表头部。当需要淘汰一个页面时,将链表头部的页面丢弃。
    LRU基于链表和哈希表的实现
    package collection.List;
    
    import java.util.HashMap;
    
    public class LRU<T> {
    
        private int capacity;
        private HashMap<Integer, ListNode> map;
        private ListNode head; // 链表的头结点,next指向链表中的第一个元素
        private ListNode tail; //链表的尾节点,链表的最后一个元素的next指向tail
        // 设置一个head和tail是为了方便对链表中的头结点和尾节点进行操作。
    
        private class ListNode { //双向链表,用来存储缓存的值
            int key;
            int val;
            ListNode prev;
            ListNode next;
            public ListNode(){
    
            }
            public ListNode(int key, int val) {
                this.key = key;
                this.val = val;
            }
        }
        public LRU(int capacity) {
            this.capacity = capacity;
            map = new HashMap<>();
            head = new ListNode();
            tail = new ListNode();
            head.next = tail; // 初始情况下,头结点的next指向tail表示链表当前为空
            tail.prev = head; // 初始情况下,tail节点的prev指向head
        }
    
        private void removeNode(ListNode node) { // 在链表中的移除node节点
            node.prev.next = node.next;
            node.next.prev = node.prev;
        }
    
        private void addNodeToLast(ListNode node) {
            node.prev = tail.prev; // 首先将node的前驱节点设置为链表中的最后一个节点
            node.prev.next = node; // 然后设置插入节点为原链表中最后一个节点的next
            node.next = tail; //更新tail为当前插入节点的next
            tail.prev= node; //更新tail的prev域
        }
    
        public void  moveNodeToLast(ListNode node) {
            removeNode(node); // 删除原节点
            addNodeToLast(node); //将原节点的副本加入到链表尾部
        }
    
        public int get(int key) {
            if(map.containsKey(key)) { // 如果值存在,则将其移动到链表尾部,并返回值
                ListNode node = map.get(key);
                moveNodeToLast(node);
                return node.val;
            } else {
                return -1;
            }
        }
    
        public void put(int key, int value) {
            if(map.containsKey(key)){ // 如果哈希表记录中找到缓存中包含key
                ListNode node = map.get(key);
                node.val = value; // 则重新设置key所对应的value
                map.replace(key, node);
                moveNodeToLast(node); // 将原节点删除,并将其副本加入链表尾部
                return;
            }
            if(map.size() == capacity){ // 如果哈希表中没有找到缓存,且链表满,则将链头节点移除,并在哈希表中将其删除
                map.remove(head.next.key);
                removeNode(head.next);
            }
    
            ListNode node = new ListNode(key, value);
            map.put(key, node); // 将新节点用哈希表记录
            addNodeToLast(node); // 新节点加入链表尾部
        }
    }
    
    时间并不会因为你的迷茫和迟疑而停留,就在你看这篇文章的同时,不知道有多少人在冥思苦想,在为算法废寝忘食,不知道有多少人在狂热地拍着代码,不知道又有多少提交一遍又一遍地刷新着OJ的status页面…… 没有谁生来就是神牛,而千里之行,始于足下!
  • 相关阅读:
    Android WiFi系统【转】
    Android Wifi简单的梳理【转】
    深入浅出
    ubuntu16.04固定IP与设置DNS【转】
    Linux内核同步【转】
    android的GPS代码分析JNI如何HAL之间如何设置回调函数【转】
    基于android的GPS移植调用关系【转】
    【转】使用XCODE 的SOURCE CONTROL 做版本控制 (1)
    Objective-C 记录
    【转】Xcode重构功能怎么用我全告诉你
  • 原文地址:https://www.cnblogs.com/bianjunting/p/14662066.html
Copyright © 2011-2022 走看看