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. 【贪心】Emergency Evacuation

    题目 大致题意 把指定的人从同一出口送出车外,且同一位置不能同时有两个人,求所需的最短时间. 分析 第一感觉就是利用贪心思想解决问题,但是这道题的数据范围用模拟的话肯定是会爆掉的,所以这是不可取的.我 ...

  2. 比Minikube更快,使用Kind快速创建K8S学习环境

    简述 K8S 如火如荼的发展着,越来越多人想学习和了解 K8S,但是由于 K8S 的入门曲线较高很多人望而却步. 然而随着 K8S 生态的蓬勃发展,社区也呈现了越来越多的部署方案,光针对生产可用的环境 ...

  3. java中执行cmd命令

    一.java执行cmd命令的三种方式:http://www.jb51.net/article/80829.htm 参考:https://www.cnblogs.com/zhufu9426/p/7928 ...

  4. JAVA死锁排查-性能测试问题排查思路

    死锁原因 Java发生死锁的根本原因是:在申请锁时发生了交叉闭环申请.即线程在获得了锁A并且没有释放的情况下去申请锁B,这时,另一个线程已经获得了锁B,在释放锁B之前又要先获得锁A,因此闭环发生,陷入 ...

  5. 总结几个移动端H5软键盘的大坑

    1.部分机型软键盘弹起挡住原来的视图 解决方法:可以通过监听移动端软键盘弹起 Element.scrollIntoView() 方法让当前的元素滚动到浏览器窗口的可视区域内.参数如下. true,表示 ...

  6. Mybatis源码初探——优雅精良的骨架

    @ 目录 前言 精良的Mybatis骨架 宏观设计 基础支撑 日志 日志的加载 日志的使用 数据源 数据源的创建 池化技术原理 数据结构 获取连接 回收连接 缓存 缓存的实现 CacheKey 反射 ...

  7. antd图标库按需加载的插件实现

    前景概要 antd是阿里出品的一款基于antd的UI组件库,使用简单,功能丰富,被广泛应用在中台项目开发中,虽然也出现了彩蛋事故,但不能否认antd本身的优秀,而我们公司在实际工作中也大量使用antd ...

  8. mongodb--创建用户权限

    最近在部署MongoDB Replica Set,马上就到生产环境了,一想还没有给数据库设置用户权限,配置的这一路踩了好多坑,希望对大家有帮助 1. 配置好mongodb replica set 安装 ...

  9. Java中的堆和栈以及堆栈的区别

    在正式内容开始之前要说明一点,我们经常所说的堆栈堆栈是堆和栈统称,堆是堆,栈是栈,合在一起统称堆栈: 1.栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方.与C++不同,Jav ...

  10. hihoCoder 1062 最近公共祖先·一 最详细的解题报告

    题目来源:最近公共祖先·一 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 题目描述 小Ho最近发现了一个神奇的网站!虽然还不够像58同城那样神奇,但这个网站仍然让小Ho乐在其 ...