首页 > 其他分享 >5分钟手撸一个简单、易用的缓存组件,架构师都对我刮目相看

5分钟手撸一个简单、易用的缓存组件,架构师都对我刮目相看

时间:2022-11-30 14:01:56浏览次数:44  
标签:缓存 cacheMap 对象 value 易用 key 架构师 public

/**

  • 将对象加入到缓存

  • @param key 键

  • @param value 对象

  • @param timeout 过期时间

*/

void put(K key, V value, long timeout);

/**

  • 从缓存中获得对象

  • @param key 键

  • @return 键对应的对象

*/

V get(K key);

/**

  • 从缓存中删除对象

  • @param key 键

*/

void remove(K key);

}

紧接着我们继续来实现缓存的大致龙骨

缓存框架实现


1. 设置最大缓存数量

需要一个属性来存储这个容量值。

// 容量大小

private int capacity;

2. 选择合适容器来放置缓存对象

我们还用Map类型存储,新增一个Map类型属性

// 存储缓存对象

private Map<K, V> cacheMap;

3. 选择合适淘汰策略

这里我选用实现简单的LFU(least frequently used) 最少使用率策略,来说明一下大致原理,其他实现类似的。

LFU:根据使用次数来判定对象是否被持续缓存(使用率是通过访问次数计算),当缓存满时清理过期对象,清理后依旧满的情况下清除最少访问(访问计数最小)的对象并将其他对象的访问数减去这个最小访问数,以便新对象进入后可以公平计数。

这里要做到LFU,需要以下属性:

  • value 缓存对象

  • latestTime 最新访问时间 ,用于判断对象是否过期,惰性删除的时候使用。

  • count 访问次数 ,用于LFU判断。

  • ttl 对象存活时长,用于判断对象是否过期,惰性删除的时候使用。

代码如下:

public class CacheObject<V> {

/**

  • 缓存对象

*/

V value;

/**

  • 最新访问时间

*/

long latestTime;

/**

  • 访问次数

*/

int count;

/**

  • 对象存活时长,0表示永久存活

*/

private final long ttl;

/**

  • 构造

  • @param value 值

  • @param ttl 超时时长

*/

protected CacheObject(V value, long ttl) {

this.value = value;

this.ttl = ttl;

this.latestTime = System.currentTimeMillis();

}

整体实现代码框架

我们整理下上面的思路,转化为代码龙骨

/**

  • LFU 最少使用率策略

  • @param <K>

  • @param <V>

*/

public class LFUCache<K, V> implements ICache<K, V> {

// 容量大小

private int capacity;

// 存储缓存对象

private Map<K, CacheObject<V>> cacheMap;

public LFUCache(int capacity) {

this.capacity = capacity;

this.cacheMap = new HashMap<>(capacity, 1.0f);

}

@Override

public void put(K key, V value, long timeout) {

}

@Override

public V get(K key) {

return null;

}

@Override

public void remove(K key) {

}

}

有了这个龙骨,那么下面我们就根据它来把空白的地方填满吧,继续实现丰满我们的组件吧!

丰满相关实现


1. 将对象加入到缓存put操作

相关流程如下

在这里插入图片描述

伪代码如下

public void put(K key, V value, long timeout) {

if (缓存满了) {

删除所有的存活到期的对象();

if(缓存满了) {

删除一个访问次数最少的对象();

}

}

cacheMap.put(key, new CacheObject<>(value, timeout));

}

关键点

  • 存活到期对象的判断

CacheObject.latestTime + CacheObject.ttl < System.currentTimeMillis()

  • 删除访问次数最少的对象时,将其他对象的访问数减去这个最小访问数,否则老的数据越来越大,对新进入对象不公平,随着时间的增长,还有可能溢出

2. 从缓存中获得对象get操作

相关流程如下:

在这里插入图片描述

大致代码如下

public V get(K key) {

CacheObject<V> cacheObject = cacheMap.get(key);

if (cacheObject == null) {

return null;

}

cacheObject.latestTime = System.currentTimeMillis();

cacheObject.count++;

return cacheObject.value;

}

关键点

  • 每次访问到的对象,要更新最新访问时间访问次数

3. 从缓存中删除对象remove操作

这里简单的调用Map删除接口即可

public void remove(K key) {

cacheMap.remove(key);

}

好了,写到这儿,我们的缓存组件就基本成型了。

为什么说基本成型?

如果你真的是用心在看的话,估计会提问了卧槽,你这个压根线程非安全啊?

是的,那么我们来给每个方法加个synchronized即可,jdk1.6之后synchronized性能很强劲的。

ails/111273049)整体代码如下


public class LFUCache<K, V> implements ICache<K, V> {

// 容量大小

private int capacity;

// 存储缓存对象

private Map<K, CacheObject<V>> cacheMap;

public LFUCache(int capacity) {

this.capacity = capacity;

this.cacheMap = new HashMap<>(capacity, 1.0f);

}

@Override

public synchronized void put(K key, V value, long timeout) {

if (isFull()) {

pruneCache();

}

cacheMap.put(key, new CacheObject<>(value, timeout));

}

@Override

public synchronized V get(K key) {

CacheObject<V> cacheObject = cacheMap.get(key);

if (cacheObject == null) {

return null;

}

cacheObject.latestTime = System.currentTimeMillis();

cacheObject.count++;

return cacheObject.value;

}

@Override

public synchronized void remove(K key) {

cacheMap.remove(key);

}

public boolean isFull() {

return (capacity > 0) && (cacheMap.size() >= capacity);

}

protected int pruneCache() {

int count = 0;

CacheObject<V> comin = null;

// 清理过期对象并找出访问最少的对象

Iterator<CacheObject<V>> values = cacheMap.values().iterator();

CacheObject<V> co;

while (values.hasNext()) {

co = values.next();

if (co.isExpired() == true) {

values.remove();

count++;

continue;

}

//找出访问最少的对象

if (comin == null || co.count < comin.count) {

comin = co;

}

}

// 减少所有对象访问量,并清除减少后为0的访问对象

if (isFull() && comin != null) {

long minAccessCount = comin.count;

values = cacheMap.values().iterator();

CacheObject<V> co1;

while (values.hasNext()) {

co1 = values.next();

co1.count -= minAccessCount;

if (co1.count <= 0) {

values.remove();

count++;

}

}

}

return count;

}

}

验证


测试代码如下:

//通过实例化对象创建

LFUCache<String, String> lfuCache = new LFUCache<String, String>(3);

lfuCache.put("key1", "value1", DateUnit.SECOND.getMillis() * 3);

lfuCache.get("key1");//使用次数+1

lfuCache.put("key2", "value2", DateUnit.SECOND.getMillis() * 3);

lfuCache.put("key3", "value3", DateUnit.SECOND.getMillis() * 3);

lfuCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3);

//由于缓存容量只有3,当加入第四个元素的时候,根据LRU规则,最少使用的将被移除(2,3被移除)

String value1 = lfuCache.get("key1");

System.out.println(value1);//key1

String value2 = lfuCache.get("key2");

System.out.println(value2);//null

标签:缓存,cacheMap,对象,value,易用,key,架构师,public
From: https://blog.51cto.com/u_15767198/5898901

相关文章

  • 京东云缓存JIMDB建设之路
    缓存的大背景 缓存在软件应用特别是在互联网应用中无处不在,从数据库到应用服务、再到前端的页面每一层都会使用缓存进行加速,即使是硬件产品比如CPU、磁盘、网卡等也都会......
  • Abp分布式实体缓存
    直接缓存实体对象注册实体缓存:.context.Services.AddEntityCache<Product,Guid>()注入和使用.IEntityCache<Product,Guid>服务使用此用法,实体类应可序列化/可序列化......
  • Thinkphp入门 四 —布局、缓存、系统变量 (48)
    【控制器操作方法参数设置】​​http://网址/index.php/控制器/操作方法​​  【页面跳转】【变量调节器】Smarty变量调节器TP变量调节器:普通的php函数(count strlen ......
  • .net 7 获取所有缓存键的问题?
    这里是群友提供听说是issue提的问题,高手回答的。varcoherentState=_cache.GetType().GetField("_coherentState",BindingFlags.NonPublic|BindingFlags.Instance);......
  • 数据库和缓存的一致性如何保证
    最近帮组里做讲座预约系统,虽然使用人数不多,但终于还是遇到了一些系统经典问题,比如数据库与缓存的一致性问题,很有意思,好记性不如烂笔头,学习了一些思路以后决定记录下来与大......
  • 使用LRU算法缓存图片,android 3.0
    在您的UI中显示单个图片是非常简单的,如果您需要一次显示很多图片就有点复杂了。在很多情况下(例如使用ListView,GridView或者 ​​​ViewPager​​​控件),显示在屏幕......
  • 定时清除linux内存buff/cache缓存
    1.创建脚本文件       vimclean.sh2.在文件中输入以下脚本#!/bin/bash#每两小时清除一次内存buff/cache缓存echo"开始清除缓存"sync;sync;sync#写......
  • 代码块缓存机制
    id的作用:获取内存地址namis作用:用于判断两个地址是否相等i1=1000i2=1000print(i1isi2)>>>False e='taibai'print(id(name))is作用:用于判断两个地址是否相等i......
  • redis + 注解自动缓存
    1、redis配置引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></depende......
  • django-缓存
    缓存缓存的作用是缓解服务器压力,或者者说是数据库的压力,我们可以将一些常用的页面或数据放入缓存中,用户查询时,直接去缓存里面查,以此来缓解服务器压力 django提供的缓存......