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. 通用Mapper与分页插件的集成

    SpringBoot 是为了简化 Spring 应用的创建.运行.调试.部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖 ...

  2. python中lambda匿名函数与函数之间的关系

  3. java语言基础(九)_final_权限_内部类

    final关键字 final关键字代表最终.不可改变的. 常见四种用法: 可以用来修饰一个类 可以用来修饰一个方法 还可以用来修饰一个局部变量 还可以用来修饰一个成员变量 1)修饰一个类 public ...

  4. PBR(基于物理的渲染)学习笔记

    PBR基本介绍 PBR代表基于物理的渲染,本质上还是 gl_FragColor = Emssive + Ambient + Diffuse + Specular 可能高级一些在考虑下AO也就是环境光遮 ...

  5. 干!垃圾微软!发布我的Netcore跨平台UI框架 CPF

    什么鬼,我的CPF快写好了,你居然也要搞跨平台UI框架?什么Maui? 之前怎么不早说要搞跨平台UI框架呢?看到谷歌搞flutter眼红了?明年年底发布?又搞这种追别人屁股的烂事情. 什么MVU模式? ...

  6. 一题搞定static关键字

    基础不牢,地动山摇 开篇一道题,考察代码执行顺序: public class Parent { static { System.out.println("Parent static init ...

  7. dva的简介

    dva的定义 dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装; redux 他是react当中的仓库,如果熟悉vue的话,他的功能 ...

  8. C++ 字符串中子串个数

    子串可重叠情况: int fun1(const std::string& str, const std::string& sub){ int num = 0; for (size_t ...

  9. C#读取Excel转为DataTable

    需要的Dll: NPOI.OOXML.dll    https://files.cnblogs.com/files/CityOfThousandFires/NPOI.dl.rar /// <su ...

  10. JS基础知识点(一)

    原始类型 null undefined boolean number string symbol 注意 原始类型存储的都是值,是没有函数可以调用的,但实际上除null和undefined外,其他类型使 ...