随笔 - 169  文章 - 0  评论 - 292

GuavaCache学习笔记一:自定义LRU算法的缓存实现

 

前言

今天在看GuavaCache缓存相关的源码,这里想到先自己手动实现一个LRU算法。于是乎便想到LinkedHashMap和LinkedList+HashMap, 这里仅仅是作为简单的复习一下。

LRU

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

代码实现原理

LinkedList + HashMap: LinkedList其实是一个双向链表,我们可以通过get和put来设置最近请求key的位置,然后hashMap去存储数据
LinkedHashMap:LinkedHashMap是继承自HashMap,只不过Map中的Node节点改为了双向节点,双向节点可以维护添加的顺序,在LinkedHashMap的构造函数中有一个accessOrder, 当设置为true后,put和get会自动维护最近请求的位置到last。

LinkedList+HashMap代码实现

LRUCache接口:

/**
* @Description:
* @Author: wangmeng
* @Date: 2018/12/8-10:49
*/
public class LinkedListLRUTest {
public static void main(String[] args) {
LRUCache<String, String> cache = new LinkedListLRUCache<>(3);
cache.put("1", "1");
cache.put("2", "2");
cache.put("3", "3");
System.out.println(cache); cache.put("4", "4");
System.out.println(cache); System.out.println(cache.get("2"));
System.out.println(cache);
}
}

LinkedList实现:

/**
* @Description:使用LinkedList+HashMap来实现LRU算法
* @Author: wangmeng
* @Date: 2018/12/8-10:41
*/
public class LinkedListLRUCache<K, V> implements LRUCache<K, V> { private final int limit;
private final LinkedList<K> keys = new LinkedList<>();
private final Map<K, V> cache = Maps.newHashMap(); public LinkedListLRUCache(int limit) {
this.limit = limit;
} @Override
public void put(K key, V value) {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(value);
if (keys.size() >= limit) {
K oldesKey = keys.removeFirst();
cache.remove(oldesKey);
} keys.addLast(key);
cache.put(key, value);
} @Override
public V get(K key) {
boolean exist = keys.remove(key);
if (!exist) {
return null;
} keys.addLast(key);
return cache.get(key);
} @Override
public void remove(K key) { boolean exist = keys.remove(key);
if (exist) {
keys.remove(key);
cache.remove(key);
}
} @Override
public int size() {
return keys.size();
} @Override
public void clear() {
keys.clear();
cache.clear();
} @Override
public int limit() {
return this.limit;
} @Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (K key : keys) {
builder.append(key).append("=").append(cache.get(key)).append(";");
}
return builder.toString();
}
}

LinkedList测试类:

/**
* @Description:
* @Author: wangmeng
* @Date: 2018/12/8-10:49
*/
public class LinkedListLRUTest {
public static void main(String[] args) {
LRUCache<String, String> cache = new LinkedListLRUCache<>(3);
cache.put("1", "1");
cache.put("2", "2");
cache.put("3", "3");
System.out.println(cache); cache.put("4", "4");
System.out.println(cache); System.out.println(cache.get("2"));
System.out.println(cache);
}
}

LinkedList测试类返回值:

1=1;2=2;3=3;
2=2;3=3;4=4;
2
3=3;4=4;2=2;

LinkedHashMap实现

/**
* @Description: 不是一个线程安全的类,这里是使用LinkedHashMap来做LRU算法
* @Author: wangmeng
* @Date: 2018/12/8-10:14
*/
public class LinkedHashLRUCache<K, V> implements LRUCache<K, V> { private static class InternalLRUCache<K, V> extends LinkedHashMap<K, V> { final private int limit;
private InternalLRUCache(int limit) {
super(16, 0.75f, true);
this.limit = limit ;
} //实现remove元素的方法,这个是重写了LinkedHashMap中的方法。因为在HashMap的putVal会调用afterNodeInsertion(), 而这个方法会判断removeEldestEntry方法。
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > limit;
}
} private final int limit;
//使用组合关系优于继承,这里只对外暴漏LRUCache中的方法
private final InternalLRUCache<K, V> internalLRUCache;
public LinkedHashLRUCache(int limit) {
Preconditions.checkArgument(limit > 0, "The limit big than zero.");
this.limit = limit;
this.internalLRUCache = new InternalLRUCache(limit); } @Override
public void put(K key, V value) {
this.internalLRUCache.put(key, value);
} @Override
public V get(K key) {
return this.internalLRUCache.get(key);
} @Override
public void remove(K key) {
this.internalLRUCache.remove(key);
} @Override
public int size() {
return this.internalLRUCache.size();
} @Override
public void clear() {
this.internalLRUCache.clear();
} @Override
public int limit() {
return this.limit;
} @Override
public String toString() {
return internalLRUCache.toString();
}
}

LinkedHashMap测试类:

/**
* @Description:
* @Author: wangmeng
* @Date: 2018/12/8-10:30
*/
public class LinkedHashLRUTest {
public static void main(String[] args) {
LRUCache<String, String> cache = new LinkedHashLRUCache<>(3);
cache.put("1", "1");
cache.put("2", "2");
cache.put("3", "3");
System.out.println(cache); cache.put("4", "4");
System.out.println(cache); System.out.println(cache.get("2"));
System.out.println(cache);
}
}

LinkedHashMap测试结果:

{1=1, 2=2, 3=3}
{2=2, 3=3, 4=4}
2
{3=3, 4=4, 2=2}

文章目录
简介
实现LRU
LinkedHashMap中LRU算法实现
简介
LRU全称是Least Recently Used,即最近最久未使用的意思。

LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。

实现LRU
1.用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。

2.利用一个链表来实现,每次新插入数据的时候将新数据插到链表的头部;每次缓存命中(即数据被访问),则将数据移到链表头部;那么当链表满的时候,就将链表尾部的数据丢弃。

3.利用链表和hashmap。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。

对于第一种方法,需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。对于第二种方法,链表在定位数据的时候时间复杂度为O(n)。所以在一般使用第三种方式来是实现LRU算法。

LinkedHashMap中LRU算法实现
/**
* @Author: Kingcym
* @Description: 非线程安全
* @Date: 2018/11/11 19:09
*/
public class LinkedHashLRUcache<k, v> {
/**
* LinkedHashMap(自身实现了LRU算法)
* 1.有序
* 2.每次访问一个元素,都会提到最后面去
*/
private static class InternalLRUcache<k, v> extends LinkedHashMap<k, v> {
private final int limit;

private InternalLRUcache(int limit) {
super(16, 0.75f, true);
this.limit = limit;
}

//是否删除最老的数据
@Override
protected boolean removeEldestEntry(Map.Entry<k, v> eldest) {
return size() > limit;
}
}

private final int limit;
private final InternalLRUcache<k, v> internalLRUcache;

public LinkedHashLRUcache(int limit) {
Assert.state(limit > 0, "limit必须大于0");
this.limit = limit;
this.internalLRUcache = new InternalLRUcache(limit);
}

public void put(k key, v value) {
this.internalLRUcache.put(key, value);
}

public v get(k key) {
return this.internalLRUcache.get(key);
}

public void remove(k key) {
this.internalLRUcache.remove(key);
}

public int size() {
return this.internalLRUcache.size();
}

public void clear() {
this.internalLRUcache.clear();
}

public String toString() {
return internalLRUcache.toString();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。

参考:GuavaCache学习笔记一:自定义LRU算法的缓存实现

Guava---缓存之LRU算法的更多相关文章

  1. Android图片缓存之Lru算法

    前言: 上篇我们总结了Bitmap的处理,同时对比了各种处理的效率以及对内存占用大小.我们得知一个应用如果使用大量图片就会导致OOM(out of memory),那该如何处理才能近可能的降低oom发 ...

  2. Android图片缓存之Lru算法(二)

    前言: 上篇我们总结了Bitmap的处理,同时对比了各种处理的效率以及对内存占用大小.我们得知一个应用如果使用大量图片就会导致OOM(out of memory),那该如何处理才能近可能的降低oom发 ...

  3. 动手实现 LRU 算法,以及 Caffeine 和 Redis 中的缓存淘汰策略

    我是风筝,公众号「古时的风筝」. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 那天我在 LeetCode 上刷到一道 LRU 缓存机制的问题, ...

  4. 缓存淘汰算法--LRU算法

    1. LRU1.1. 原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是"如果数据最近被访问过,那么将来被访问的几率也 ...

  5. 借助LinkedHashMap实现基于LRU算法缓存

    一.LRU算法介绍 LRU(Least Recently Used)最近最少使用算法,是用在操作系统中的页面置换算法,因为内存空间是有限的,不可能把所有东西都放进来,所以就必须要有所取舍,我们应该把什 ...

  6. 简单LRU算法实现缓存

    最简单的LRU算法实现,就是利用jdk的LinkedHashMap,覆写其中的removeEldestEntry(Map.Entry)方法即可,如下所示: java 代码 import java.ut ...

  7. 使用guava实现找回密码的tokenCache以及LRU算法

    源码包的简单说明: com.google.common.annotations:普通注解类型. com.google.common.base:基本工具类库和接口. com.google.common. ...

  8. GuavaCache学习笔记一:自定义LRU算法的缓存实现

    前言 今天在看GuavaCache缓存相关的源码,这里想到先自己手动实现一个LRU算法.于是乎便想到LinkedHashMap和LinkedList+HashMap, 这里仅仅是作为简单的复习一下. ...

  9. LRU算法 缓存淘汰策略

    四种实现方式 LRU 1.1. 原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也 ...

  10. LRU算法---缓存淘汰算法

    计算机中的缓存大小是有限的,如果对所有数据都缓存,肯定是不现实的,所以需要有一种淘汰机制,用于将一些暂时没有用的数据给淘汰掉,以换入新鲜的数据进来,这样可以提高缓存的命中率,减少磁盘访问的次数. LR ...

随机推荐

  1. 【02】Python:数据类型和运算符

    写在前面的话 任何编程语言一开始都是从概念出发的,但各种编程语言之间的概念可能又会有差异,所以,老生常谈,我们还是需要从新过一遍 Python 的概念,当然,如果你已经是老司机了,完全可以一晃而过,不 ...

  2. 【05】C#特有的ref、out参数

    java和C#非常相似,它们大部分的语法是一样的,但尽管如此,也有一些地方是不同的. 为了更好地学习java或C#,有必要分清它们两者到底在哪里不同. 我们这次要来探讨C#特有的ref.out参数. ...

  3. 2 java并行基础

    我们认真研究如何才能构建一个正确.健壮并且高效的并行系统. 进程与线程 进程(Process):是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础 ...

  4. Asp.net core 简单介绍

    Asp.net core 是一个开源和跨平台的框架,用于构建如WEB应用,物联网(IoT)应用和移动后端应用等连接到互联网的基于云的现代应用程序.asp.net core 应用可运行.net和.net ...

  5. 《Real World Haskell》内容脉络整理

    p.s.  其实就是28–(6入门[1~4,6])-(10暂不用[17,20~28])=13网页啊! 1~ 6 章: 语法入门 (类型,函数,表达式语法糖,typeclass) 7~13章:  熟练/ ...

  6. Vue.js最佳实践--给大量子孙组件传值(provide/inject)

    开发中有个需求,有个Parent组件(例如div)下,输入框,下拉框,radiobutton等可编辑的子孙组件几百个,根据某个值统一控制Parent下面的所有控件的disabled状态 类似于这样,给 ...

  7. android studio学习---怎么创建一个新的module并且再次运行起来(在当前的project里面)

    选择File->new module出现的界面,选择android application选择下一步,就出现了和刚刚一样的流程了,一步步创建完成即可. 我们看到多了个secondAndroid的 ...

  8. JVM Code Cache空间不足,导致服务性能变慢

    本文阅读时间大约5分钟. 有业务反馈,线上一个应用运行了一段时间之后,在高峰期之后,突然发现处理能力下降,接口的响应时间变长,但是看Cat上的GC数据,一切都很正常. 通过跳板机上机器查看日志,发现一 ...

  9. Java集合学习(10):hashcode() 和 equals()方法

    哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个 ...

  10. MySQL修炼之路一

    1. MySQL概述 1. 什么是数据库 存储数据的仓库 2. 都有哪些公司在用数据库 金融机构.游戏网站.购物网站.论坛网站 ... ... 3. 提供数据库服务的软件 1. 软件分类 MySQL. ...