前言

之前也用过一些缓存中间件,框架,也想着自己是不是也能用Java写一个出来,于是就有了这个想法,打算在写的过程中同步进行总结。

源码:weloe/Java-Distributed-Cache (github.com)

本篇代码:

Java-Distributed-Cache/src/main/java/com/weloe/cache/outstrategy at master · weloe/Java-Distributed-Cache (github.com)

Java-Distributed-Cache/src/test/java/com/weloe/cache/outstrategy at master · weloe/Java-Distributed-Cache (github.com)

我们可以想想几个问题,什么是缓存?为什么需要缓存?

什么是缓存?将之前请求的数据暂存,遇到同样的请求/状况直接返回,这就是缓存。

为什么需要?同样的情况下,直接返回数据,无需其他操作,能加快服务器反应速度,减轻服务器压力。

那么缓存怎么存?简单的缓存为键值对,可以用Map存储。这就完了吗?如果我们一直往Map中存储数据,占用的内存会越来越大,这时候怎么办?

这就是本篇需要解决的问题。

要使用缓存,就必然会面临到缓存使用空间达到上限的问题,这个时候就需要从已有的缓存数据中淘汰一部分去维持缓存的可用性。

LRU

力扣上的相关题 https://leetcode.cn/problems/lru-cache/

LRU,缓存淘汰算法,最近最少使用(Least Recently Used),就是一种选择淘汰数据的策略

原理:为最近被访问的数据进行缓存,淘汰不常被访问的数据。

也就是说我们认为最近使用过的数据应该是有用的,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。

举一个我们最常见的例子,手机可用把软件放到后台运行,比如我们先后打开了日历,设置,闹钟。后台的顺序是 日历->设置->闹钟,如果这台手机只能打开三个应用,再打开 应用商城 ,后台的顺序会变成 应用商城->日历->设置。

从这个案例可以知道LRU的主要两个操作的具体思路,一个数据结构存值,一个数据结构存储后台顺序。

缓存一般以key,value形式存储,因此选择map存储,而存储顺序的数据结构由于要不断改动节点顺序,选择双向链表

public class LRUCache<K, V> implements CacheStrategy<K, V> {
private Map<K, V> map;
private int capacity;
private Deque<K> queue;
private Callback callback; public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap();
this.queue = new LinkedList();
}
}

put(key,value)

如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

   	public void put(int key, int value) {
if (map.containsKey(key)) {
queue.remove(key);
}
queue.addFirst(key);
map.put(key, value); // 缓存达到上限
if (queue.size() > capacity) { // 移除
K last = queue.removeLast();
V removeValue = map.remove(last); // 回调
if (callback != null) {
callback.callback(last, removeValue);
}
}
return value; }

get(key)

如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1

    public V get(K key) {
// 如果已经缓存过该数据
if (map.containsKey(key)) {
queue.remove(key);
queue.addFirst(key);
return map.get(key);
}
return null;
}

弊端,容易出现缓存污染问题

(k1,v1) (k2,v2),(k3,v3),(k4,v4)

(k2,v2),(k4,v4),(k1,v1),(k3,v3)

LRU-K

LRU-K算法是对LRU算法的改进,将原先进入缓存队列的评判标准从访问一次改为访问K次。

LRU-K算法有两个队列,一个是缓存队列,一个是数据访问历史队列。当访问一个数据时,首先先在访问历史队列中累加访问次数,当历史访问记录超过K次后,才将数据缓存至缓存队列,从而避免缓存队列被污染。同时访问历史队列中的数据可以按照LRU的规则进行淘汰。具体如下:

public class LRUKCache<K, V> extends LRUCache<K, V> {

    // 进入缓存队列的评判标准
private int putStandard; // 访问数据历史记录
private LRUCache<Object, Integer> historyList; public LRUKCache(int cacheSize, int historyCapacity, int putStandard) {
super(cacheSize);
this.putStandard = putStandard;
this.historyList = new LRUCache(historyCapacity);
} @Override
public V get(K key) {
// 记录数据访问次数
Integer historyCount = historyList.get(key);
historyCount = historyCount == null ? 0 : historyCount;
historyList.put(key, ++historyCount);
return super.get(key);
} @Override
public V put(K key, V value) {
if (value == null) {
return null;
}
// 如果已经在缓存里则直接返回
if (super.get(key) != null) {
return super.put(key, value);
}
// 如果数据历史访问次数达到上限,则加入缓存
Integer historyCount = historyList.get(key);
historyCount = (historyCount == null) ? 0 : historyCount;
if (removeCache(historyCount)) {
// 移除历史访问记录,加入缓存
historyList.remove(key);
return super.put(key, value);
} return value;
} private boolean removeCache(Integer historyCount) {
return historyCount >= putStandard;
} public void setPutStandard(int putStandard) {
this.putStandard = putStandard;
} @Override
public void setCallback(Callback<K, V> callback) {
super.setCallback(callback);
} public void setHistoryListCallback(Callback<K, V> callback) {
historyList.setCallback((Callback<Object, Integer>) callback);
} }

LRU-K能降低缓存污染发生的概率,但是需要额外记录对象访问次数,内存消耗较大。

测试

class LRUCacheTest {

    @Test
void lru(){
CacheStrategy<Integer, Integer> lruCache = new LRUCache<>(5);
lruCache.setCallback((integer, integer2) -> System.out.println("淘汰"+integer+"="+integer2));
lruCache.put(1,1);
lruCache.put(2,2);
lruCache.put(3,3);
lruCache.put(4,4);
lruCache.put(5,5);
lruCache.put(6,6);
List list = lruCache.list();
System.out.println(list);
} }
淘汰1=1
[2=2, 3=3, 4=4, 5=5, 6=6]
class LRUKCacheTest {

    @Test
void lrukCacheTest() {
LRUKCache<Integer, Integer> lrukCache = new LRUKCache<>(2,3,1);
lrukCache.setHistoryListCallback((integer, integer2) -> System.out.println("记录队列淘汰"+integer+"="+integer2));
lrukCache.setCallback((integer, integer2) -> System.out.println("缓存淘汰"+integer+"="+integer2));
lrukCache.get(1);
lrukCache.get(1);
lrukCache.get(1);
lrukCache.get(2);
lrukCache.get(2);
lrukCache.get(2);
lrukCache.get(3);
lrukCache.get(3);
lrukCache.get(3);
lrukCache.get(4);
lrukCache.get(4);
lrukCache.get(4);
lrukCache.put(1,2);
lrukCache.put(2,2);
lrukCache.put(3,2);
lrukCache.put(4,2);
List list = lrukCache.list();
System.out.println(list);
}
}
记录队列淘汰1=3
缓存淘汰2=2
[3=2, 4=2]

用Java写一个分布式缓存——缓存淘汰算法的更多相关文章

  1. 用JAVA写一个函数,功能例如以下: 随意给定一组数, 找出随意数相加之后的结果为35(随意设定)的情况

    用JAVA写一个函数.功能例如以下:随意给定一组数,比如{12,60,-8,99,15,35,17,18},找出随意数相加之后的结果为35(随意设定)的情况. 能够递归算法来解: package te ...

  2. 五:用JAVA写一个阿里云VPC Open API调用程序

    用JAVA写一个阿里云VPC Open API调用程序 摘要:用JAVA拼出来Open API的URL 引言 VPC提供了丰富的API接口,让网络工程是可以通过API调用的方式管理网络资源.用程序和软 ...

  3. 用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载

    用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载,将一个完整的项目进行展示,主要有以下几个部分: 1.servlet部分   Export 2.工具类:TxtFileU ...

  4. 用JAVA写一个多线程程序,写四个线程,其中二个对一个变量加1,另外二个对一个变量减1

    package com.ljn.base; /** * @author lijinnan * @date:2013-9-12 上午9:55:32 */ public class IncDecThrea ...

  5. 使用JAVA写一个简单的日历

    JAVA写一个简单的日历import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateF ...

  6. Java实现一个简单的缓存方法

    缓存是在web开发中经常用到的,将程序经常使用到或调用到的对象存在内存中,或者是耗时较长但又不具有实时性的查询数据放入内存中,在一定程度上可以提高性能和效率.下面我实现了一个简单的缓存,步骤如下. 创 ...

  7. 【redis前传】自己手写一个LRU策略 | redis淘汰策略

    title: 自己手写一个LRU策略 date: 2021-06-18 12:00:30 tags: - [redis] - [lru] categories: - [redis] permalink ...

  8. java 写一个"HelloJavaWorld你好世界"输出到操作系统文件Hello.txt文件中

    package com.beiwo.homework; import java.io.File; import java.io.FileOutputStream; import java.io.IOE ...

  9. Java写一个简单学生管理系统

    其实作为一名Java的程序猿,无论你是初学也好,大神也罢,学生管理系统一直都是一个非常好的例子,初学者主要是用数组.List等等来写出一个简易的学生管理系统,二.牛逼一点的大神则用数据库+swing来 ...

  10. 用java写一个用户登陆界面

    一.课堂测试源代码及其结果截图 用java的swing写一个用户登录界面,采用网格布局.源代码如下: /** * */package LiuLijia; import java.awt.CardLay ...

随机推荐

  1. 作用域通信对象:session用户在登录时通过`void setAttribute(String name,Object value)`方法设置用户名和密码。点击登录按钮后,跳转到另外一个页面显示用户

    作用域通信对象:session session对象基于会话,不同用户拥有不同的会话.同一个用户共享session对象的所有属性.作用域开始客户连接到应用程序的某个页面,结束与服务器断开连接.sessi ...

  2. 齐博x1标签动态调用数据

    示例代码如下: {qb:tag name="news_list_page_listdata02" type="cms" union="fid" ...

  3. 【性能测试】Loadrunner12.55(二)-飞机订票系统-脚本录制

    1.1 飞机订票系统 Loadrunner 12.55不会自动安装飞机订票系统,要自己手动安装. 我们需要下载Web Tools以及一个小插件strawberry https://marketplac ...

  4. 在CentOS7下安装Oracle11教程

    前言 安装oracle时,发现网上的文章总是缺少一些信息,导致安装不顺利,因为我对一些文章进行了整合,用以备忘. Oracle安装 首先下载linux版本的oracle安装文件,然后通过XFTP上传到 ...

  5. Docker | 专栏文章整理🎉🎉

    Docker Docker系列文章基本已经更新完毕,这是我从去年的学习笔记中整理出来的. 笔记稍微有点杂乱.随意,把它们整理成文章花费了不少力气.整理的过程也是我的一个再次学习的过程,同时也是为了方便 ...

  6. SQLSever数据库基本操作

    一.SQLSever数据库基本操作 1.创建数据库 use master if exists(select * from sysdatabases where name='SMDB') drop da ...

  7. Latex数学公式学习

    要想博客写的更详细,更好,那么具体详细的数学推导这一部分是少不了的,不仅要好看还要方便输入那些更为复杂的符号,因此学习Latex就是必不可少的啦,说不定过几天就要用嘞! 本篇文章参考自超详细 LaTe ...

  8. php 程序员进化之路

    1.目标明确 2.消除干扰 3.自我激励 鸟哥 --2018年11月17日php年会

  9. [排序算法] 冒泡排序 (C++)

    冒泡排序解释: 冒泡排序 BubbleSort 是一种最基础的交换排序.顾名思义,数组中的每一个元素就好像泡泡一样,根据自己的大小不同一点点的向一侧移动. 冒泡排序原理: 每一趟只能确定将一个数归位. ...

  10. SpringCloud(十一)- 秒杀 抢购

    1.流程图 1.1 数据预热 1.2 抢购 1.3 生成订单 (发送订单消息) 1.4 订单入库 (监听 消费订单消息) 1.5 查看订单状态 1.6 支付 (获取支付链接 ) 1.7 支付成功 微信 ...