LRU cache

LRU(最近最少使用)是一种常用的缓存淘汰机制。当缓存大小容量到达最大分配容量的时候,就会将缓存中最近访问最少的对象删除掉,以腾出空间给新来的数据。

实现

(1)单线程简单版本

( 来源:力扣(LeetCode)链接:leetcode题目)

  题目: 设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
       写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

  思路:LinkedList + HashMap: LinkedList用来保存key的访问情况,最近访问的key将会放置到链表的最尾端,如果链表大小超过容量,移除链表的第一个节点,同时移除该key在hashmap中对应的键值对。程序如下:

class LRUCache {
private HashMap<Integer, Integer> hashMap = null;
private LinkedList<Integer> list = null;
private int capacity;
public LRUCache(int capacity) {
hashMap = new HashMap<>(capacity);
list = new LinkedList<Integer>();
this.capacity = capacity;
} public int get(int key) {
if(hashMap.containsKey(key)){
list.remove((Object)key);
list.addLast(key);
return hashMap.get(key);
}
return -1;
} public void put(int key, int value) {
if(list.contains((Integer)key)){
list.remove((Integer)key);
list.addLast((Integer)key);
hashMap.put(key, value);
return;
}
if(list.size() == capacity){
Integer v = list.get(0);
list.remove(0);
hashMap.remove((Object)v);
}
list.addLast(key);
hashMap.put(key, value);
}
}

(2)多线程并发版LRU Cache

 与单线程思路类似,将HashMap和LinkedList换成支持线程安全的容器ConcurrentHashMap和ConcurrentLinkedQueue结构。ConcurrentLinkedQueue是一个基于链表,支持先进先出的的队列结构,处理方法同单线程类似,只不过为了保证多线程下的安全问题,我们会使用支持读写分离锁的ReadWiterLock来保证线程安全。它可以实现:

  1.同一时刻,多个线程同时读取共享资源。

  2.同一时刻,只允许单个线程进行写操作。

/*
* 泛型中通配符
* ? 表示不确定的 java 类型
* T (type) 表示具体的一个java类型
* K V (key value) 分别代表java键值中的Key Value
* E (element) 代表Element
*/
public class MyLRUCache<K, V> {
private final int capacity;
private ConcurrentHashMap<K, V> cacheMap;
private ConcurrentLinkedQueue<K> keys;
ReadWriteLock RWLock = new ReentrantReadWriteLock();
/*
* 读写锁
*/
private Lock readLock = RWLock.readLock();
private Lock writeLock = RWLock.writeLock(); private ScheduledExecutorService scheduledExecutorService; public MyLRUCache(int capacity) {
this.capacity = capacity;
cacheMap = new ConcurrentHashMap<>(capacity);
keys = new ConcurrentLinkedQueue<>();
scheduledExecutorService = Executors.newScheduledThreadPool(10);
} public boolean put(K key, V value, long expireTime){
writeLock.lock();
try {
//需要注意containsKey和contains方法方法的区别
if(cacheMap.containsKey(key)){
keys.remove(key);
keys.add(key);
cacheMap.put(key, value);
return true;
}
if(cacheMap.size() == capacity){
K tmp = keys.poll();
if( key != null){
cacheMap.remove(tmp);
}
}
cacheMap.put(key, value);
keys.add(key);
if(expireTime > 0){
removeAfterExpireTime(key, expireTime);
}
return true;
}finally {
writeLock.unlock();
}
} public V get(K key){
readLock.lock();
try {
if(cacheMap.containsKey(key)){
keys.remove(key);
keys.add(key);
return cacheMap.get(key);
}
return null;
}finally {
readLock.unlock();
}
} public boolean remove(K key){
writeLock.lock();
try {
if(cacheMap.containsKey(key)){
cacheMap.remove(key);
keys.remove(key);
return true;
}
return false;
}finally {
writeLock.unlock();
}
} private void removeAfterExpireTime(K key, long expireTime){
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
cacheMap.remove(key);
keys.remove(key);
}
}, expireTime, TimeUnit.MILLISECONDS);
}
public int size(){
return cacheMap.size();
}
}

  在代码中添加了设置键值对失效的put方法,通过使用一个定时器线程池保证过期键值对的及时清理。测试代码如下:

public class LRUTest {
public static void main(String[] args) throws InterruptedException {
/*
MyLRUCache<String, Integer> myLruCache = new MyLRUCache(100000);
ExecutorService es = Executors.newFixedThreadPool(10);
AtomicInteger atomicInteger = new AtomicInteger(1);
CountDownLatch latch = new CountDownLatch(10);
long starttime = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
es.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100000; j++) {
int v = atomicInteger.getAndIncrement();
myLruCache.put(Thread.currentThread().getName() + "_" + v, v, 200000);
}
latch.countDown();
}
});
} latch.await();
long endtime = System.currentTimeMillis();
es.shutdown();
System.out.println("Cache size:" + myLruCache.size()); //Cache size:1000000
System.out.println("Time cost: " + (endtime - starttime));
*/
MyLRUCache<Integer, String> myLruCache = new MyLRUCache<>( 10);
myLruCache.put(1, "Java", 1000);
myLruCache.put(2, "C++", 2000);
myLruCache.put(3, "Java", 3000);
System.out.println(myLruCache.size());//3
Thread.sleep(2200);
System.out.println(myLruCache.size());//1
}
}

  

LRU cache缓存简单实现的更多相关文章

  1. LeetCode题解: LRU Cache 缓存设计

    LeetCode题解: LRU Cache 缓存设计 2014年12月10日 08:54:16 邴越 阅读数 1101更多 分类专栏: LeetCode   版权声明:本文为博主原创文章,遵循CC 4 ...

  2. LRU Cache的简单c++实现

    什么是 LRU LRU Cache是一个Cache的置换算法,含义是“最近最少使用”,把满足“最近最少使用”的数据从Cache中剔除出去,并且保证Cache中第一个数据是最近刚刚访问的,因为这样的数据 ...

  3. [LintCode] LRU Cache 缓存器

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  4. 基于LRU Cache的简单缓存

    package com.test.testCache; import java.util.Map; import org.json.JSONArray; import org.json.JSONExc ...

  5. Go LRU Cache 抛砖引玉

    目录 1. LRU Cache 2. container/list.go 2.1 list 数据结构 2.2 list 使用例子 3. transport.go connLRU 4. 结尾 正文 1. ...

  6. LRU Cache & Bloom Filter

    Cache 缓存 1. 记忆 2. 空间有限 3. 钱包 - 储物柜 4. 类似背代码模板,O(n) 变 O(1)     LRU Cache 缓存替换算法 1. Least Recently Use ...

  7. [LeetCode] LRU Cache 最近最少使用页面置换缓存器

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  8. LeetCode之LRU Cache 最近最少使用算法 缓存设计

    设计并实现最近最久未使用(Least Recently Used)缓存. 题目描述: Design and implement a data structure for Least Recently ...

  9. [LeetCode] 146. LRU Cache 最近最少使用页面置换缓存器

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

随机推荐

  1. sublime清空控制台

    解决方法 – 只需运行print('\n'*100)打印100个换行符,您将无法看到任何以前的输出,除非你向上滚动一些距离.

  2. docker推送镜像到私有仓库

    配置私有仓库源 私有仓库地址:registry.supos.ai 修改/etc/docker/daemon.json文件,增加insecure-registries,如下所示: { "ins ...

  3. Redis系列(六):数据结构QuickList(快速列表)源码解析

    1.介绍 Redis在3.2版本之前List的底层编码是ZipList和LinkedList实现的 在3.2版本之后,重新引入了QuickList的数据结构,列表的底层都是QuickList实现 当L ...

  4. BZOJ 4055 Misc

    原题传送门 比较复杂的一道DP. 设两点(i,j)之间最短路为dis[i][j],则 可转化为: 将该式前后分立,可得: 其中,可以单独求出,后面的部分则需要DP. 设为b(x),枚举i,并计算出从i ...

  5. day02小程序配置

    附上微信小程序开发文档的网址:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html 学技术 ...

  6. web页面弹出遮罩层,通过js或css禁止蒙层底部页面跟随滚动

    场景概述 弹窗是一种常见的交互方式,而蒙层是弹窗必不可少的元素,用于隔断页面与弹窗区块,暂时阻断页面的交互.但是,在蒙层元素中滑动的时候,滑到内容的尽头时,再继续滑动,蒙层底部的页面会开始滚动,显然这 ...

  7. 奇妙的 CSS MASK

    本文将介绍 CSS 中一个非常有意思的属性 mask . 顾名思义,mask 译为遮罩.在 CSS 中,mask 属性允许使用者通过遮罩或者裁切特定区域的图片的方式来隐藏一个元素的部分或者全部可见区域 ...

  8. Instrction Arrangement UDH 4109 拓扑排序 or 最长路

    题目描述 Ali has taken the Computer Organization and Architecture course this term. He learned that ther ...

  9. JAVA基础笔记10-11-12-13-14

    十.今日内容介绍 1.继承 2.抽象类 3.综合案例---员工类系列定义 01继承的概述 *A:继承的概念 *a:继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系 *b:在J ...

  10. WPF手机号码归属批量查询并导出到Excel

    工具下载地址:https://download.csdn.net/download/m0_37137902/12589801 1WPF页面xaml代码 <Window x:Class=" ...