LRU算法
LRU(Least recently used)—最近最少被使用算法。在一些内存算法表现中若在一个内存有限的环境中,出现内存不足时为了保证程序的正常运行,会将一些价值较低的对象(如何来评定一个对象的价值的高低,对于LRU算法来说:如果数据最近被访问过,那么将来被访问的几率也更高,那么对应的价值就越高)进行清除,空出内存空间。
计算机操作系统基础理论中有一个经典理论:最近使用的页面数据会在未来一段时期内仍然被使用,已经很久没有使用的页面很有可能在未来较长的一段时间内仍然不会被使用。
2:LRU算法应用实现
在缓存应用中,使用LRU算法可以保证缓存数据量的可控,一旦缓存数据总量超过指定阀值,就将最近最少被访问的数据给清除掉。下面的具体的实现都基于cache需求来实现
2.1:Java中cache的实现(HashMap+双向链表实现)
实现思路:使用HashMap中value存储双向链表的各个节点,双向链表按照数据写入顺序的数据存储,一旦任意数据被访问,获取HashMap中该数据所在节点,将此节点移动到head(表示该数据最近被访问到),重新连接该节点上下节点。最终经常访问的数据会在双向链表前端聚齐,而不经常被访问的数据会聚集在链表尾部,一旦出现数据总量超过阀值时,我们可以通过将尾部的数据直接移除,来保证整个缓存的健康。
这里为何选用HashMap+双向链表来实现:
- HashMap的k,v结构对应这cache中k,v的概念,所以使用HashMap中V存储双向链表中的Node
- HashMap中查询写入的时间复杂度为O(1)
- 双向链表中的节点的移动,删除的时间复杂度为O(1)。
- 双向链表可以使我们通过任意一个节点获取其它所有节点
源码:
/**
* 基于HashMap+双向链表
* @author fangyuan
*/
public class MyLRUCache<K,V> {
/**
* 用于存储所有节点数据【利用HashMap中读写为O(1)特性】可以快速获取节点
*/
private HashMap<K,Node<K,V>> nodes;
/**
* 用于存储双向链表头
*/
private Node<K, V> head;
/**
* 用于存储双向链尾
*/
private Node<K, V> tail;
/**
* 缓存中最大数据深度
*/
private Integer maxSize;
public MyLRUCache(Integer maxSize){
if( ( maxSize =(maxSize ==null ? 16:maxSize))<6){
throw new RuntimeException("缓存最大过小,最小值为6");
}
nodes = new HashMap<K,Node<K,V>>(maxSize,0.75F);
this.maxSize = maxSize;
//初始化
this.head = null;
this.tail = null;
}
/**
* 定义双向链表结构
*/
class Node<K, V> {
private K key;
private V value;
private Node<K, V> before;
private Node<K, V> after;
public Node(K k,V v){
this.key = k;
this.value =v;
}
}
public void putCaceh(K k,V v){
//创建数据节点
Node<K,V> node = new Node<>(k,v);
//将当前节点加入双向链表头
this.addNewNode(node);
//判断是否内存缓存超过最大深度
if(nodes.size()>this.maxSize){
//移除尾部数据
removeTailNode();
}
}
public Integer size(){
return nodes.size();
}
private void removeTailNode() {
this.tail.before.after = null;
this.nodes.remove(tail.key);
this.tail = tail.before;
}
private void addNewNode(Node<K,V> node) {
if(nodes.containsKey(node.key)){
//该key已经存在 替换值并
Node cnode = nodes.get(node.key);
cnode.value = node.value;
afterNodeAccess(cnode);
}else {
//是一个新值只需要追加到双向链表头即可
if(head != null){
this.head.before = node;
node.after = this.head;
if(tail == null ){
tail = head;
}
this.head = node;
}else {
this.head = node;
this.tail = this.head;
}
}
nodes.put(node.key,node);
}
/**
* 获取元素
* @param t
* @return
*/
public V getCache(K t){
Node<K,V> node = nodes.get(t);
if(node != null){
V v = node.value;
//将当前节点放到双向链表头部
this.afterNodeAccess(node);
return v;
}
return null;
}
/**
* 将node写入双向链表头
* @param node
*/
private void afterNodeAccess(Node<K,V> node) {
Node p = node,a = p.after, b=p.before;
p.before = null;
//进行替换
//说明当前节点是head
if(b == null){
return;
}else {
b.after = a;
}
//说明当前节点为tail
if(a == null ){
this.tail = b;
}else{
a.before = b;
}
head.before = p;
p.after = head;
head = p;
}
/**
* 循环迭代
*/
public void forEach(BiConsumer<? super K, ? super V> action) {
if (action == null) {
throw new NullPointerException();
}
for (Node<K,V> e = head; e != null; e = e.after) {
action.accept(e.key, e.value);
}
}
}
测试代码:
public static void main(String[] args) {
int maxSize = 6 ;
MyLRUCache myLRUCache = new MyLRUCache(maxSize);
//构建测试数据
for (int i=0;i<6;i++){
myLRUCache.putCaceh("test_key_"+i,i);
}
myLRUCache.getCache("test_key_1");
myLRUCache.getCache("test_key_5");
myLRUCache.putCaceh("test_key_7",7);
myLRUCache.putCaceh("test_key_8",8);
myLRUCache.getCache("test_key_1");
myLRUCache.getCache("test_key_5");
myLRUCache.putCaceh("test_key_9",9);
myLRUCache.putCaceh("test_key_10",10);
myLRUCache.getCache("test_key_1");
myLRUCache.getCache("test_key_5");
myLRUCache.getCache("test_key_8");
myLRUCache.putCaceh("test_key_5",55);
//按照LRU算法 最终的结果为 55(最新数据),8,1,10,9,7(最old数据)
myLRUCache.forEach((k,v)->{
System.out.print(v+",");
});
}
最终运行的结果如下图所示:
最终结果与期盼的结果值保持一致
2.2:基于Java中LinkedHashMap实现
从Java中LinkedHashMap底层实现可以知道LinkedHashMap的排序特性和LRU算法不谋而合。
/**
*使用LinkedHashMap实现LRU算法
*
* @author fangyuan
*
*/
public class LRUCache extends LinkedHashMap {
/**
* 使用LRU算法构建的缓存中保存的最大数据
*/
private Integer maxSize;
public LRUCache( Integer maxSize){
super(16,0.75F,true);
this.maxSize = maxSize;
}
/**
* 重写removeEldestEntry,按照LRU算法,当达到内存最大深度时,移除最年长的数据(不经常被访问的数据)
* @param eldest
* @return
*/
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return this.maxSize<this.size();
}
}
测试代码:
/**
* LRUCache测试
* @author fangyuan
*/
public class LRUCacheTest {
public static void main(String[] args) {
int maxSize = 6 ;
LRUCache lruCache = new LRUCache(maxSize);
//构建测试数据
for (int i=0;i<6;i++){
lruCache.put("test_key_"+i,i);
}
lruCache.get("test_key_1");
lruCache.get("test_key_5");
lruCache.put("test_key_7",7);
lruCache.put("test_key_8",8);
lruCache.get("test_key_1");
lruCache.get("test_key_5");
lruCache.put("test_key_9",9);
lruCache.put("test_key_10",10);
lruCache.get("test_key_1");
lruCache.get("test_key_5");
lruCache.get("test_key_8");
//按照LRU算法 最终的结果为 7(最old数据),9,10,1,5,8(最新数据)
lruCache.forEach((k,v)->{
System.out.print(v+",");
});
}
}
最终运行的结果如下图所示:

最终结果与期盼的结果值保持一致
1032



被折叠的 条评论
为什么被折叠?



