不吹不黑,这个算法,你肯定不会

IT科技类资讯2025-11-05 13:54:4042723

01、不吹不黑前言

我们常用缓存提升数据查询速度,个算由于缓存容量有限,法肯当缓存容量到达上限,不吹不黑就需要删除部分数据挪出空间,个算这样新数据才可以添加进来。法肯缓存数据不能随机删除,不吹不黑一般情况下我们需要根据某种算法删除缓存数据。个算常用淘汰算法有 LRU,法肯LFU,FIFO,这篇文章我们聊聊 LRU 算法。

02、不吹不黑LRU 简介

LRU 是个算 Least Recently Used 的缩写,这种算法认为最近使用的法肯数据是热门数据,下一次很大概率将会再次被使用。不吹不黑而最近很少被使用的个算数据,很大概率下一次不再用到。法肯当缓存容量满的时候,优先淘汰最近很少使用的数据。

假设现在缓存内部数据如图所示:

这里我们将列表第一个节点称为头结点,最后一个节点为尾结点。

当调用缓存获取 key=1 的数据,LRU 算法需要将 1 这个节点移动到头结点,其余节点不变,如图所示。

然后我们插入一个 key=8 节点,此时缓存容量到达上限,所以加入之前需要先删除数据。IT技术网由于每次查询都会将数据移动到头结点,未被查询的数据就将会下沉到尾部节点,尾部的数据就可以认为是最少被访问的数据,所以删除尾结点的数据。

然后我们直接将数据添加到头结点。

这里总结一下 LRU 算法具体步骤:

新数据直接插入到列表头部 缓存数据被命中,将数据移动到列表头部 缓存已满的时候,移除列表尾部数据。

03、LRU 算法实现

上面例子中可以看到,LRU 算法需要添加头节点,删除尾结点。而链表添加节点/删除节点时间复杂度 O(1),非常适合当做存储缓存数据容器。但是不能使用普通的单向链表,单向链表有几点劣势:

每次获取任意节点数据,都需要从头结点遍历下去,这就导致获取节点复杂度为 O(N)。 移动中间节点到头结点,我们需要知道中间节点前一个节点的信息,单向链表就不得不再次遍历获取信息。

针对以上问题,可以结合其他数据结构解决。

使用散列表存储节点,b2b信息网获取节点的复杂度将会降低为 O(1)。节点移动问题可以在节点中再增加前驱指针,记录上一个节点信息,这样链表就从单向链表变成了双向链表。

综上使用双向链表加散列表结合体,数据结构如图所示:

在双向链表中特意增加两个『哨兵』节点,不用来存储任何数据。使用哨兵节点,增加/删除节点的时候就可以不用考虑边界节点不存在情况,简化编程难度,降低代码复杂度。

LRU 算法实现代码如下,为了简化 key ,val 都认为 int 类型。

public class LRUCache {  Entry head, tail;  int capacity;  int size;  Map cache;  public LRUCache(int capacity) {  this.capacity = capacity;  // 初始化链表  initLinkedList();  size = 0;  cache = new HashMap<>(capacity + 2);  }  /**  * 如果节点不存在,返回 -1.如果存在,将节点移动到头结点,并返回节点的数据。  *  * @param key  * @return  */  public int get(int key) {  Entry node = cache.get(key);  if (node == null) {  return -1;  }  // 存在移动节点  moveToHead(node);  return node.value;  }  /**  * 将节点加入到头结点,如果容量已满,将会删除尾结点  *  * @param key  * @param value  */  public void put(int key, int value) {  Entry node = cache.get(key);  if (node != null) {  node.value = value;  moveToHead(node);  return;  }  // 不存在。先加进去,再移除尾结点  // 此时容量已满 删除尾结点  if (size == capacity) {  Entry lastNode = tail.pre;  deleteNode(lastNode);  cache.remove(lastNode.key);  size--;  }  // 加入头结点  Entry newNode = new Entry();  newNode.key = key;  newNode.value = value;  addNode(newNode);  cache.put(key, newNode);  size++;  }  private void moveToHead(Entry node) {  // 首先删除原来节点的关系  deleteNode(node);  addNode(node);  }  private void addNode(Entry node) {  head.next.pre = node;  node.next = head.next;  node.pre = head;  head.next = node;  }  private void deleteNode(Entry node) {  node.pre.next = node.next;  node.next.pre = node.pre;  }  public static class Entry {  public Entry pre;  public Entry next;  public int key;  public int value;  public Entry(int key, int value) {  this.key = key;  this.value = value;  }  public Entry() {  }  }  private void initLinkedList() {  head = new Entry();  tail = new Entry();  head.next = tail;  tail.pre = head;  }  public static void main(String[] args) {  LRUCache cache = new LRUCache(2);  cache.put(1, 1);  cache.put(2, 2);  System.out.println(cache.get(1));  cache.put(3, 3);  System.out.println(cache.get(2));  } } 

04、LRU 算法分析

缓存命中率是缓存系统的非常重要指标,如果缓存系统的缓存命中率过低,将会导致查询回流到数据库,网站模板导致数据库的压力升高。

结合以上分析 LRU 算法优缺点。

LRU 算法优势在于算法实现难度不大,对于对于热点数据, LRU 效率会很好。

LRU 算法劣势在于对于偶发的批量操作,比如说批量查询历史数据,就有可能使缓存中热门数据被这些历史数据替换,造成缓存污染,导致缓存命中率下降,减慢了正常数据查询。

05、LRU 算法改进方案

以下方案来源于 MySQL InnoDB LRU 改进算法

将链表拆分成两部分,分为热数据区,与冷数据区,如图所示。

改进之后算法流程将会变成下面一样:

访问数据如果位于热数据区,与之前 LRU 算法一样,移动到热数据区的头结点。 插入数据时,若缓存已满,淘汰尾结点的数据。然后将数据插入冷数据区的头结点。 处于冷数据区的数据每次被访问需要做如下判断: 若该数据已在缓存中超过指定时间,比如说 1 s,则移动到热数据区的头结点。 若该数据存在在时间小于指定的时间,则位置保持不变。

对于偶发的批量查询,数据仅仅只会落入冷数据区,然后很快就会被淘汰出去。热门数据区的数据将不会受到影响,这样就解决了 LRU 算法缓存命中率下降的问题。

其他改进方法还有 LRU-K,2Q,LIRS 算法,感兴趣同学可以自行查阅。

本文地址:http://www.bhae.cn/news/256f21699527.html
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

全站热门

假如是在虚拟机中使用Ubuntu,那么设置之前请先参照我的上一遍文章虚拟机Net方式设置连接外网 中的网络设置部分,先设置好主机的网络,然后配置虚拟机Ubuntu的IP和网关 假如主机操作系统就是Ubuntu,请直接参照下文进行设置 内容如下: 1. 检验是否可以连通,就使用ping命令ping 网关 永久修改Ubuntu LINUX IP 一、使用命令设置ubuntu的ip地址 并用下面的行来替换有关eth0的行: # The primary network interface - use DHCP to find our address auto eth0 iface eth0 inet dhcp 用下面的命令使网络设置生效: sudo /etc/init.d/networking restart 也可以在命令行下直接输入下面的命令来获取地址 sudo dhclient eth0 /etc/network/interfaces: sudo vi /etc/network/interfaces 并用下面的行来替换有关eth0的行: # The primary network interface auto eth0 iface eth0 inet static address 203.171.239.155 gateway 203.171.239.129 netmask 255.255.255.224 #network 203.171.239.128 #broadcast 192.168.3.159 将上面的ip地址等信息换成你自己就可以了.用下面的命令使网络设置生效: sudo /etc/init.d/networking restart /etc/network/interfaces: sudo vi /etc/network/interfaces 在该文件中添加如下的行: auto eth0:1 iface eth0:1 inet static address 192.168.1.155 netmask 255.255.255.0 work x.x.x.x broadcast x.x.x.x gateway x.x.x.x 根据你的情况填上所有诸如address,netmask,network,broadcast和gateways等信息. 用下面的命令使网络设置生效: sudo /etc/init.d/networking restart PS:根据上文方式,在虚拟机Vmware中安装Ubuntu,虚拟机网络连接方式为Net,设置静态IP连接网络,我已经测试通过,直接修改resolv.con和interfaces的方式可以保证开机后设置的IP依然存在。 设置后不能连接网络,欢迎留言,共同讨论,呵呵!

Linux下限制Shell:Rssh和Scponly

Python项目自动化部署最佳实践

数据库扩展策略

新贵EM200(探索EM200的性能、设计与便利性,领略电动车的新境界)

MongoDB聚合管道:从平均值计算到复杂数据处理的利器

如何在MacBook Pro Retina上安装Linux?

Monitorix:一款面向Linux的轻型系统和网络监测工具

友情链接

滇ICP备2023000592号-9