链表实现一个简单的LRU(java)

438 阅读3分钟

LRU简介

LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。 该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间t,当须淘汰一个页面时,选择现有页面中其t 值最大的,即最近最少使用的页面予以淘汰。

Redis中的LRU

当需要从缓存中淘汰数据时,我们希望淘汰那些将来不可能再被使用的数据,保留那些将来还会频繁访问的数据。

一个解决方法就是通过LRU进行预测:最近被频繁访问的数据将来被访问的可能性也越大。缓存中的数据一般会有这样的访问分布:一部分数据拥有绝大部分的访问量。

当访问模式很少改变时,可以记录每个数据的最后一次访问时间,拥有最少空闲时间的数据可以被认为将来最有可能被访问到。

配置参数

Redis配置中和LRU有关的有三个:

  • maxmemory:配置Redis存储数据时指定限制的内存大小,比如100m。当缓存消耗的内存超过这个数值时, 将触发数据淘汰。如果配置为0时,表示缓存的数据量没有限制。
  • maxmemory_policy:触发数据淘汰后的淘汰策略
  • maxmemory_samples: 随机采样的精度,也就是随即取出key的数目。该数值配置越大, 越接近于真实的LRU算法,但是数值越大,相应消耗也变高,对性能有一定影响。

淘汰策略

淘汰策略即maxmemory_policy的赋值有以下几种:

  • noeviction:如果缓存数据超过了maxmemory限定值,并且客户端正在执行的命令(大部分的写入指令,但DEL和几个指令例外)会导致内存分配,则向客户端返回错误响应
  • allkeys-lru: 对所有的键都采取LRU淘汰
  • volatile-lru: 仅对设置了过期时间的键采取LRU淘汰
  • allkeys-random: 随机回收所有的键
  • volatile-random: 随机回收设置过期时间的键
  • volatile-ttl: 仅淘汰设置了过期时间的键---淘汰生存时间TTL(Time To Live)更小的键

链表实现一个简单的LRU

package com.stone.java001;

public class LRUForLinkList {
    
    private LRUNode headNode;
    // 容量大小,类比与redis设置的maxmemory大小
    private Integer capacity;
    // 实际元素长度
    private Integer length;

    public LRUForLinkList() {
        this.headNode = new LRUNode();
        this.capacity = 10;
        this.length = 0;
    }

    //插入数据
    public void add(Integer data) {
        //判断要插入的数据是否存在,如果存在,删除存在的元素,将该元素插入进头部
        //如果不存在,判断是否超出容量,如果超出容量,删除最后一个元素,将该元素插入头部
        LRUNode preNode = findPreNode(data);
        if (preNode != null) {
            delNode(preNode);
            insertBegin(data);
        } else {
            if (length >= capacity) {
                delEndNode();
            }
            insertBegin(data);
        }
    }

    //在头部插入元素
    public void insertBegin(Integer data) {
        this.headNode.setNext(new LRUNode(data, this.headNode.getNext()));
        length++;
    }

    //找到该元素的前一个节点
    public LRUNode findPreNode(Integer data) {
        LRUNode node = this.headNode;
        while (node.getNext() != null) {
            if (data.equals(node.getNext().getData())) {
                return node;
            }
            node = node.getNext();
        }
        return null;
    }

    //删除指定值的元素
    public void delNode(LRUNode preNode) {
        LRUNode tmp = preNode.getNext();
        preNode.setNext(tmp.getNext());
        tmp = null;
        length--;
    }

    //删除末尾节点
    public void delEndNode() {
        LRUNode ptr = headNode;
        if (ptr.getNext() == null) {
            return;
        }

        while (ptr.getNext().getNext() != null) {
            ptr = ptr.getNext();
        }

        LRUNode tmp = ptr.getNext();
        ptr.setNext(null);
        tmp = null;
        length--;
    }

    //打印该链表
    private void printAll() {
        LRUNode node = headNode.getNext();
        while (node != null) {
            System.out.print(node.getData() + ",");
            node = node.getNext();
        }
        System.out.println();
    }


    //测试。。。
    public static void main(String[] args) {
        LRUForLinkList lruForLinkList = new LRUForLinkList();
        for(int i=0;i<10;i++){
            lruForLinkList.add(i);
        }
        lruForLinkList.printAll();
        lruForLinkList.add(8);
        lruForLinkList.printAll();
        lruForLinkList.add(11);
        lruForLinkList.printAll();
    }
}

class LRUNode {
    private Integer data;
    private LRUNode next;

    public LRUNode() {
        this.next = null;
    }

    public LRUNode(Integer data) {
        this.data = data;
    }

    public LRUNode(Integer data, LRUNode next) {
        this.data = data;
        this.next = next;
    }

    public Integer getData() {
        return data;
    }

    public void setData(Integer data) {
        this.data = data;
    }

    public LRUNode getNext() {
        return next;
    }

    public void setNext(LRUNode next) {
        this.next = next;
    }
}