欢迎光临
我们一直在努力

模拟实现LRUCache

目录

LRUCache

概念

实现原理

基于LinkedHashMap实现LRUCache

主要特点

构造函数

完整代码

代码示例1–基于插入顺序

代码示例2–基于访问顺序

代码示例3–删除最少访问

模拟实现LRUCache

基本框架

存放节点–put()

 将节点移至开头–moveToHead()

删除指定节点–removeNode()

将节点放到开头–addToHead()

删除尾部节点–removeTail()

 访问指定节点–get()

 打印LRUCache

完整代码

代码示例

应用场景

LRUCache优缺点


LRUCache
概念

LRUCache,即最近最少使用缓存(Least Recently Used Cache),是一种常用的缓存替换策略。在缓存系统中,由于缓存空间有限,当缓存满时,需要按照一定的策略替换掉部分旧数据,以便为新数据腾出空间。LRUCache正是基于“最近最少使用”的原则来选择被替换的数据,即认为最近一段时间未被访问的数据在未来被访问的可能性也最小,因此优先替换这些数据。

实现原理

LRUCache的实现通常依赖于哈希表和双向链表两种数据结构的结合。哈希表用于实现数据的快速查找,而双向链表则用于维护数据的使用顺序。具体实现方式如下:

1.哈希表:哈希表的键(Key)为缓存数据的键,值(Value)为双向链表中对应节点的指针或迭代器。这样,在访问缓存数据时,可以通过哈希表快速定位到双向链表中的节点,进而进行访问或更新操作。 2.双向链表:双向链表中的节点按照数据被访问的顺序排列,最近被访问的节点位于链表头部,而最久未被访问的节点则位于链表尾部。当访问或更新某个节点时,将该节点从当前位置删除,并插入到链表头部,以表示该节点是最近被访问的。当缓存满时,则直接删除链表尾部的节点,即最久未被访问的节点,以腾出空间。

使用双向链表是因为双向链表可以实现任意位置O(1)的插入和删除,使用哈希表是因为哈希表的增删查改也是O(1)。 

基于LinkedHashMap实现LRUCache

LinkedHashMap 是 Java 集合框架中的一部分,它实现了 Map 接口,并且保持了一个运行于所有条目的双重链接列表。这意味着迭代器会以元素被插入的顺序进行遍历(插入顺序),或者根据访问顺序(访问顺序,即最近最少使用(LRU)缓存策略)来遍历,具体取决于其构造时的配置。

主要特点

1.有序性:LinkedHashMap 维护了一个插入顺序的链表,这使得它能够按照元素被插入的顺序来遍历键值对。如果你需要保持元素插入的顺序,那么 LinkedHashMap 是一个很好的选择。 2.访问顺序:通过构造函数的一个布尔参数,你可以指定 LinkedHashMap 是否应该按照访问顺序来排序键值对,即最近访问的元素会被放在链表的末尾。这种特性使得 LinkedHashMap 非常适合实现缓存,特别是最近最少使用(LRU)缓存。 3.性能:与 HashMap 相比,LinkedHashMap 的性能略低,因为它需要维护一个额外的链表来保持元素的顺序。然而,在需要保持元素顺序的场景下,这种性能损失通常是可接受的。 4.继承结构:LinkedHashMap 继承自 HashMap,因此它继承了 HashMap 的大部分功能,包括快速的查找、插入和删除操作。

构造函数

1.LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder):这个构造函数允许你指定初始容量、加载因子和是否按照访问顺序来排序键值对。 2.LinkedHashMap(int initialCapacity, float loadFactor):这个构造函数与上一个类似,但是它默认不使用访问顺序。 3.LinkedHashMap(int initialCapacity):这个构造函数仅指定了初始容量,加载因子被设置为默认值(0.75f),并且不使用访问顺序。 4.LinkedHashMap():使用默认初始容量(16)和加载因子(0.75f) , 并且不使用访问顺序。 

完整代码

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache extends LinkedHashMap<Integer, Integer>{

public int capacity;
public LRUCache(int capacity) {
//这个的true 代表 基于访问顺序
super(capacity,0.75F,true);
this.capacity = capacity;
}

@Override
public Integer get(Object key) {
return super.getOrDefault(key,-1);
}

@Override
public Integer put(Integer key, Integer value) {
return super.put(key, value);
}

@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
}

代码示例1–基于插入顺序

public static void main(String[] args) {
LinkedHashMap<String,Integer> linkedHashMap =
new LinkedHashMap<>(16,0.7f,false);
linkedHashMap.put("小明",10);
linkedHashMap.put("小花",11);
linkedHashMap.put("小狗",12);
System.out.println(linkedHashMap);
System.out.println("获取元素");
System.out.println(linkedHashMap.get("小花"));
System.out.println(linkedHashMap);
}

运行结果

代码示例2–基于访问顺序

public static void main(String[] args) {
LinkedHashMap<String,Integer> linkedHashMap =
new LinkedHashMap<>(16,0.7f,true);
linkedHashMap.put("小明",10);
linkedHashMap.put("小花",11);
linkedHashMap.put("小狗",12);
System.out.println(linkedHashMap);
System.out.println("获取元素");
System.out.println(linkedHashMap.get("小花"));
System.out.println(linkedHashMap);
}

运行结果:

代码示例3–删除最少访问

public static void main(String[] args) {
LRUCache lruCache = new LRUCache(3);
lruCache.put(100,10);
lruCache.put(110,11);
lruCache.put(120,12);
System.out.println(lruCache);
System.out.println("获取元素");
System.out.println(lruCache.get(110));
System.out.println(lruCache);
System.out.println(lruCache.get(100));
System.out.println(lruCache);

System.out.println("存放元素,会删除头节点,因为头节点是最近最少使用的: ");
lruCache.put(999,99);

System.out.println(lruCache);

}

运行结果:

模拟实现LRUCache
基本框架

public class MyLRUCache {

static class DLinkNode {
public int key;
public int val;
public DLinkNode prev;
public DLinkNode next;
public DLinkNode() {

}
public DLinkNode(int key, int val) {
this.key = key;
this.val = val;
}

@Override
public String toString() {
return "{ key=" + key +", val=" + val+"} ";
}
}

public DLinkNode head;//双向链表的头节点
public DLinkNode tail;//双向链表的尾节点
public int usedSize;//代表当前双向链表当中 有效的数据个数
public Map<Integer,DLinkNode> cache;//定义一个map
public int capacity;//容量

public MyLRUCache(int capacity) {
this.head = new DLinkNode();
this.tail = new DLinkNode();
head.next = tail;
tail.prev = head;
cache = new HashMap<>();
this.capacity = capacity;
}
}

存放节点–put()

public void put(int key,int val) {
//1. 查找当前的这个key 是不是存储过
DLinkNode node = cache.get(key);
//2. 如果没有存储过
if(node == null) {
//2.1 需要实例化一个节点
DLinkNode dLinkNode = new DLinkNode(key,val);
//2.2 存储到map当中一份
cache.put(key,dLinkNode);
//2.3 把该节点存储到链表的开头
addToHead(dLinkNode);
usedSize++;
//2.4 检查当前双向链表的有效数据个数 是不是超过了capacity
if(usedSize > capacity) {
//2.5 超过了,就需要移除尾部的节点
DLinkNode remNode = removeTail();
//2.6 清除cache当中的元素
cache.remove(remNode.key);
//2.7 usedSize–;
usedSize–;
}
printNodes("put");
}else {
//3. 如果存储过
//3.1 更新这个key对应的value
node.val = val;
//3.2 然后将该节点,移动至开头【因为这个是新插入的数据】
moveToHead(node);
}
}

 将节点移至开头–moveToHead()

private void moveToHead(DLinkNode node) {
//1. 先删除这个节点
removeNode(node);
//2. 添加到开头
addToHead(node);
}

删除指定节点–removeNode()

private void removeNode(DLinkNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}

将节点放到开头–addToHead()

private void addToHead(DLinkNode node) {
head.next.prev=node;
node.next=head.next;
node.prev=head;
head.next=node;
}

删除尾部节点–removeTail()

private DLinkNode removeTail() {
DLinkNode del = tail.prev;
tail.prev = del.prev;
del.prev.next = tail;
return del;
}

 访问指定节点–get()

public int get(int key) {
DLinkNode node = cache.get(key);
if(node == null) {
return -1;
}
//把最近 最多使用的 放到了链表的开头
moveToHead(node);
printNodes("get ");
return node.val;
}

 打印LRUCache

public void printNodes(String str) {
System.out.println(str+": ");
DLinkNode cur = head.next;
while (cur != tail) {
System.out.print(cur);
cur = cur.next;
}
System.out.println();
}

完整代码

import java.util.HashMap;
import java.util.Map;

public class MyLRUCache {

static class DLinkNode {
public int key;
public int val;
public DLinkNode prev;
public DLinkNode next;
public DLinkNode() {

}
public DLinkNode(int key, int val) {
this.key = key;
this.val = val;
}

@Override
public String toString() {
return "{ key=" + key +", val=" + val+"} ";
}
}

public DLinkNode head;//双向链表的头节点
public DLinkNode tail;//双向链表的尾节点
public int usedSize;//代表当前双向链表当中 有效的数据个数
public Map<Integer,DLinkNode> cache;//定义一个map
public int capacity;//容量

public MyLRUCache(int capacity) {
this.head = new DLinkNode();
this.tail = new DLinkNode();
head.next = tail;
tail.prev = head;
cache = new HashMap<>();
this.capacity = capacity;
}

public void put(int key,int val) {
//1. 查找当前的这个key 是不是存储过
DLinkNode node = cache.get(key);
//2. 如果没有存储过
if(node == null) {
//2.1 需要实例化一个节点
DLinkNode dLinkNode = new DLinkNode(key,val);
//2.2 存储到map当中一份
cache.put(key,dLinkNode);
//2.3 把该节点存储到链表的开头
addToHead(dLinkNode);
usedSize++;
//2.4 检查当前双向链表的有效数据个数 是不是超过了capacity
if(usedSize > capacity) {
//2.5 超过了,就需要移除尾部的节点
DLinkNode remNode = removeTail();
//2.6 清除cache当中的元素
cache.remove(remNode.key);
//2.7 usedSize–;
usedSize–;
}
printNodes("put");
}else {
//3. 如果存储过
//3.1 更新这个key对应的value
node.val = val;
//3.2 然后将该节点,移动至开头【因为这个是新插入的数据】
moveToHead(node);
}
}

private void moveToHead(DLinkNode node) {
//1. 先删除这个节点
removeNode(node);
//2. 添加到开头
addToHead(node);
}

private void removeNode(DLinkNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}

private void addToHead(DLinkNode node) {
head.next.prev=node;
node.next=head.next;
node.prev=head;
head.next=node;
}

private DLinkNode removeTail() {
DLinkNode del = tail.prev;
tail.prev = del.prev;
del.prev.next = tail;
return del;
}

public int get(int key) {
DLinkNode node = cache.get(key);
if(node == null) {
return -1;
}
//把最近 最多使用的 放到了链表的开头
moveToHead(node);
printNodes("get ");
return node.val;
}

public void printNodes(String str) {
System.out.println(str+": ");
DLinkNode cur = head.next;
while (cur != tail) {
System.out.print(cur);
cur = cur.next;
}
System.out.println();
}
}

代码示例

public static void main(String[] args) {
MyLRUCache lruCache = new MyLRUCache(3);
lruCache.put(100,10);
lruCache.put(110,11);
lruCache.put(120,12);
System.out.println("获取元素");
System.out.println(lruCache.get(110));
System.out.println(lruCache.get(100));
System.out.println("存放元素,会删除头节点,因为头节点是最近最少使用的: ");
lruCache.put(999,99);
}

运行结果:

应用场景

LRUCache由于其高效的缓存替换策略,被广泛应用于各种需要缓存的场景中,如:

1.Web缓存:在Web服务器中,LRUCache可以用于缓存用户请求的网页内容,减少对后端2.数据库的访问次数,提高响应速度。 3.数据库缓存:在数据库系统中,LRUCache可以用于缓存查询结果,减少数据库的I/O操作,提高查询效率。 4.操作系统缓存:在操作系统中,LRUCache可以用于缓存文件系统的数据块,减少对硬盘的访问次数,提高文件系统的性能。

LRUCache优缺点

优点:

实现简单,易于理解和维护。 缓存替换效率高,能够较好地反映数据的访问模式。 缺点:

需要额外的空间来存储哈希表和双向链表,增加了内存开销。 在某些情况下,如数据访问模式变化较大时,LRUCache的命中率可能会下降。

赞(0)
未经允许不得转载:171主机测评 » 模拟实现LRUCache
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址