当前位置:   article > 正文

Lru缓存算法的简易设计及实现_设计lru缓存

设计lru缓存


前言

LRU算法又称最近最少使用算法,它的基本思想是长期不被使用的数据,在未来被用到的几率也不大,所以当新的数据进来时我们可以优先把这些数据替换掉。


一、LinkedHashMap中key的顺序

LinkedHashMap是一个特殊的Map对象,底层会基于双向列表记录Map中的key的添加顺序或访问顺序:

1.当使用无参构造器构建LinkedHashMap对象时,存储对象默认会记录key的添加顺序,如下示例,输出结果为:{F=100,A=200,M=300}

  1. public static void main(String[] args) {
  2. LinkedHashMap<Object,Object> map=new LinkedHashMap<>();
  3. map.put("F", 100);
  4. map.put("A", 200);
  5. map.put("M", 300);
  6. map.get("A");
  7. System.out.println(map);
  8. }

2.通过带参构造方法构建LinkedHashMap对象时,会记录key的访问顺序,如下示例,输出结果为:

{F=100,A=200,M=300,P=400}

  1. public static void main(String[] args) {
  2. LinkedHashMap<Object,Object> map=new LinkedHashMap<>(3,0.75f,true);
  3. map.put("F", 100);
  4. map.put("A", 200);
  5. map.put("M", 300);
  6. map.get("A");
  7. map.put("P",400);
  8. System.out.println(map);
  9. }

3.LinkedHashMap类有一个removeEldestEntry()方法:在每次执行put方法时都会调用此方法,用于判断容器是否已经满了,该方法的返回值为布尔型,如果返回值为true,此时会先移除(最近访问最少:最早添加的且后期没有访问)再放入,默认的返回值为false

创建LinkedHashMap匿名内部类对象,再重写removeEldestEntry()方法,即可实现当容器满了,移除元素后添加,示例如下,输出结果为:

{F=100,A=200,E=400}

有了这个结果,我们就可以基于LinkedHashMap设计一个简单的Lru缓存

  1. public static void main(String[] args) {
  2. LinkedHashMap<Object,Object> map=
  3. new LinkedHashMap(3,0.75f,true){
  4. //每次执行put方法都会调用此方法,用于判断容器是否已经满了
  5. //方法返回true表示已满,此时会先移除(最近访问最少)再放入。
  6. @Override
  7. protected boolean removeEldestEntry(Map.Entry eldest) {
  8. return size()>3;//默认值是false(表示不移除,即便是满了)
  9. }
  10. };
  11. map.put("F", 100);
  12. map.put("A", 200);
  13. map.put("M", 300);
  14. map.get("F");
  15. map.get("A");
  16. map.put("E", 400);
  17. System.out.println(map);
  18. }

图示:


二、设计Lru

1.准备Cache标准接口

该接口起到规范定义的作用,代码如下:

  1. public interface Cache {
  2. void putObject(Object key,Object value);
  3. Object getObject(Object key);
  4. Object removeObject(Object key);
  5. void clear();
  6. int size();
  7. }

2.准备接口实现类

用于简易Cache实现:

  1. /**
  2. * 简易Cache实现
  3. * 1)存储结构:散列表(基于JAVA中的hashmap存储)
  4. * 2)淘汰算法:没有(直到到内存溢出)
  5. * 3)线程安全:否
  6. * 4)缓存对对象的引用?强引用
  7. * 5)对象获取:浅拷贝(获取对象地址)
  8. */
  9. public class PerpetualCache implements Cache{
  10. private HashMap<Object,Object> cache=new HashMap<>();
  11. @Override
  12. public void putObject(Object key, Object value) {
  13. cache.put(key, value);
  14. }
  15. @Override
  16. public Object getObject(Object key) {
  17. return cache.get(key);
  18. }
  19. @Override
  20. public Object removeObject(Object key) {
  21. return cache.remove(key);
  22. }
  23. @Override
  24. public void clear() {
  25. cache.clear();
  26. }
  27. @Override
  28. public int size() {
  29. return cache.size();
  30. }
  31. @Override
  32. public String toString() {
  33. return "PerpetualCache{" +
  34. "cache=" + cache.toString() +
  35. '}';
  36. }
  37. }

3.设计Lru

给定四个属性:

Cache cache,用于存储数据

int maxCap,设定最容量

LinkedHashMap<Object,Object> keyAccessOrders,用于记录key的访问顺序

Object eldEstKey,用于记录访问次数最少的key

a.当我们向cache中添加元素同时,往keyAccessOrders添加元素,keyAccessOrders的value可以按喜好添加,key必须保持与cache一致,因为key是保证顺序的关键

b.同理,当我们在访问,移除或清空cache时,keyAccessOrders需要同步操作

c.当容器满了,eldEstKey有值,且为访问次数最少的key,所以通过判断eldEstKey是否有值即可判断容器是否已满,如果满了,根据eldEstKey值移除访问次数最少的cache(keyAccessOrders会自动移除),同时将eldEstKey重新赋值为null

  1. Override
  2. protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
  3. boolean isFull= size()>maxCap;
  4. if(isFull)eldEstKey=eldest.getKey();//获取最近访问次数最少的key
  5. return isFull;
  6. }
  7. };
  8. }
  9. @Override
  10. public void putObject(Object key, Object value) {
  11. //1.向Cache中添加新的元素
  12. cache.putObject(key, value);
  13. //2.记录key的访问顺序
  14. keyAccessOrders.put(key, key);
  15. //3.Cache满了则移除最近访问次数最少的key/value
  16. if(eldEstKey!=null){
  17. cache.removeObject(eldEstKey);
  18. eldEstKey=null;
  19. }
  20. }
  21. @Override
  22. public Object getObject(Object key) {
  23. //1.记录key的访问顺序
  24. keyAccessOrders.get(key);
  25. //2.返回cache中的指定key对应的value
  26. return cache.getObject(key);
  27. }
  28. @Override
  29. public Object removeObject(Object key) {
  30. Object object = cache.removeObject(key);
  31. keyAccessOrders.remove(key);
  32. return object;
  33. }
  34. @Override
  35. public void clear() {
  36. cache.clear();
  37. keyAccessOrders.clear();
  38. }
  39. @Override
  40. public int size() {
  41. return cache.size();
  42. }
  43. @Override
  44. public String toString() {
  45. return "LruCache{" +
  46. "cache=" + cache.toString() +
  47. '}';
  48. }

如下测试,输出结果为:LruCache{cache=PerpetualCache{cache={A=100, D=400, E=500}}}

  1. public static void main(String[] args) {
  2. Cache cache=new LruCache(//负责添加算法
  3. new PerpetualCache(),//负责存数据
  4. 3);
  5. cache.putObject("A", 100);
  6. cache.putObject("B", 200);
  7. cache.putObject("C", 300);
  8. cache.getObject("A");
  9. cache.putObject("D", 400);
  10. cache.putObject("E", 500);
  11. System.out.println(cache);
  12. }
  13. }

三、使用

以实际业务为例:

客户端第一次向服务器发送请求时,控制台输出Get Data from Database,因为通过key查询缓存,查询不到对应的数据,所以向数据库请求数据,并将数据存入缓存中

当客户端再次向服务器发送请求时,只要缓存数据没有移除,则控制台不输出任何内容,因为服务器直接向缓存中拿数据了

  1. private Cache cache=new LruCache(new PerpetualCache(),3);
  2. @GetMapping("/list")
  3. public List<User> list(){
  4. Object obj = cache.getObject("userListKey");
  5. if(obj != null){
  6. return (List<User>)obj;//向下造型
  7. }
  8. System.out.println("Get Data from Database");
  9. List<User> list = mapper.list();
  10. cache.putObject("userListKey",list);
  11. return list;
  12. }


总结

如果只是设计Lru算法,简单实现,其设计方法还可以更简单,只要围绕HashLinkedMap类的key的顺序思考即可,如上设计方式比较灵活,也方便之后做增强装饰.

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/163676
推荐阅读
相关标签
  

闽ICP备14008679号