要求:

  • get(key):如果key在cache中,则返回对应的value值,否则返回null
  • set(key,value):如果key不在cache中,则将该(key,value)插入cache中(注意,如果cache已满,则必须把最近最久未使用的元素从cache中删除);如果key在cache中,则重置value的值。
  • set和get的时间复杂度都是O(1)。

两个map


/**
* 思路:时间复杂度是O(1),一下子想到的是map。但是怎么进行淘汰呢?需要记录时间,且查找的复杂度也是O(1),那也用map吧。
* put之前,需要先查看是否包含key,如果包含,删掉之前的数据,再放进新的数据,且把key的访问时间更新。淘汰最早访问数据的时候,还需要根据访问时间查询key。
* 所以,需要的操作包括:根据key获取value和access;根据access获取key。
*/
public class MyLRUMapSample<K, V> {
//cache的容量
private int capacity;
//存储key-value/access的map
private HashMap<K, Pair<V>> valueMap;
//存储access-key的map,使用SortedMap,对access进行排序
private SortedMap<Long, K> accessKeyMap; public MyLRUMapSample(){}
public MyLRUMapSample(int capacity){
this.capacity = capacity;
valueMap = new HashMap<K, Pair<V>>();
accessKeyMap = new TreeMap<Long, K>();
} public V get(K key){
if(!valueMap.containsKey(key)){
return null;
}
//修改access
Long access = accessKeyMap.lastKey() + 1;
accessKeyMap.remove(valueMap.get(key).access);
accessKeyMap.put(access, key);
valueMap.get(key).access = access;
return valueMap.get(key).value;
} public void put(K key, V value){
//如果含有值,则更新
if(valueMap.containsKey(key)){
Long oldAccess = valueMap.get(key).access;
accessKeyMap.remove(oldAccess);
valueMap.remove(key);
} //如果存储已满,则淘汰掉最早访问的
if(valueMap.size() >= capacity){
Long oldAccess = accessKeyMap.firstKey();
valueMap.remove(accessKeyMap.get(oldAccess));
accessKeyMap.remove(oldAccess);
} //添加最新的数据
Long access = accessKeyMap.isEmpty() ? 0 : accessKeyMap.lastKey() + 1;
valueMap.put(key, new Pair(access, value));
accessKeyMap.put(access, key);
} //访问时间和value值
class Pair<V>{
public Long access;
public V value;
public Pair(){}
public Pair(Long access, V value){
this.access = access;
this.value = value;
}
} public K getOldestKey(){
return accessKeyMap.isEmpty() ? null : accessKeyMap.get(accessKeyMap.firstKey());
} public K getLatestKey(){
return accessKeyMap.isEmpty() ? null : accessKeyMap.get(accessKeyMap.lastKey());
} public static void main(String[] args) {
LinkedListMapLRUSample<Integer, Integer> sample = new LinkedListMapLRUSample<Integer, Integer>(2);
Assert.check(sample.getLatestKey() == null);
Assert.check(sample.get(2) == null);
sample.put(2, 1);
sample.put(2, 2);
Assert.check(sample.get(2).intValue() == 2);
sample.put(1, 2);
sample.put(1, 3);
Assert.check(sample.get(1) == 3);
Assert.check(sample.get(2).intValue() == 2); sample.put(3, 3);
Assert.check(sample.get(1) == null);
} }

双向链表+hashMap

/**
* 使用LinkedList存储数据,从头部插入,从尾部淘汰。这样就保证了容量和淘汰规则正确性。
* 使用hashMap,通过key找到value。
*/
public class LinkedListMapLRUSample<K,V> { class Node<K, V>{
public K key;
public V value;
public Node preNode;
public Node nextNode;
public Node(){}
public Node(K key, V value, Node preNode, Node nextNode){
this.key = key;
this.value = value;
this.preNode = preNode;
this.nextNode = nextNode;
}
} private Node<K,V> head;
private Node<K,V> tail;
private HashMap<K,Node<K, V>> valueMap;
private int capacity; public LinkedListMapLRUSample(){}
public LinkedListMapLRUSample(int capacity){
this.capacity = capacity;
valueMap = new HashMap<K,Node<K, V>>();
} public void put(K key, V value){
if(head == null){
head = new Node(key, value, null, null);
tail = head;
valueMap.put(key, head);
return;
} //如果已经包含了数据,需要更新
if(valueMap.containsKey(key)){
valueMap.get(key).value = value;
moveToHead(key);
return;
} //满容量,需要淘汰掉最旧的数据
if(valueMap.size() >= capacity){
valueMap.remove(tail.key);
tail = tail.preNode;
if(tail != null){
tail.nextNode = null;
}
} insertAsHead(key, value); } private void insertAsHead(K key, V value){
//在头部添加新节点
Node target = new Node(key, value, null, head);
if(head != null){
head.preNode = target;
target.nextNode = head;
}
valueMap.put(key, target);
head = target;
} private void moveToHead(K key){
Node target = valueMap.get(key);
//头部元素
if(target.preNode == null){
return;
}
//尾部元素
if(target.nextNode == null){
tail = target.preNode;
tail.nextNode = null;
}else{
target.preNode.nextNode = target.nextNode;
target.nextNode.preNode = target.preNode;
} target.preNode = null;
target.nextNode = head;
head.preNode = target;
head = target;
} public V get(K key){
if(!valueMap.containsKey(key)){
return null;
}
moveToHead(key);
return valueMap.get(key).value;
} public K getLatestKey(){
return head == null ? null : head.key;
} public K getOldestKey(){
return tail == null ? null : tail.key;
} public static void main(String[] args) {
LinkedListMapLRUSample<Integer, Integer> sample = new LinkedListMapLRUSample<Integer, Integer>(2);
Assert.check(sample.getLatestKey() == null);
Assert.check(sample.get(2) == null);
sample.put(2,1);
sample.put(2, 2);
Assert.check(sample.get(2).intValue() == 2);
sample.put(1, 2);
sample.put(1, 3);
Assert.check(sample.get(1) == 3);
Assert.check(sample.get(2).intValue() == 2); sample.put(3, 3);
Assert.check(sample.get(1) == null);
}
}

需要根据访问时间或者插入时间进行排序时,考虑使用双向链表。

最先插入淘汰和最少访问淘汰

  • 最先插入淘汰,即FIFO。也就是要求set操作时,新数据放在head,get操作不需要移动。那么,只需要在get操作的时候直接返回valueMap中的数据即可。
  • 最少访问淘汰,是按照访问次数排序,将访问次数最少的数据删除。

    可以使用两个map实现,valueMap(HashMap)和accessCountMap(SortedMap)。accessCount初始为1,get/put时,accessCount自增,并按照accessCount排序。

如果用数组和map实现,就需要accessCount自增后,进行重排序;或者在需要淘汰的时候进行重排序。

LinkedHashMap源码解读

LinkedHashMap使用一个双向链表加一个HashMap。同时有一个成员变量accessOrder控制淘汰策略:为true 表示按照LRU方式淘汰,false表示按照FIFO方式淘汰。

另外还有一个方法控制是否删除最旧的数据:

    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
} //put新数据的时候,检查是否需要删除最旧的数据
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex); // Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}

使用LinkedHashMap实现LRUCache:

public class LinkedHashMapLRUSample<K, V> extends LinkedHashMap<K, V>{
private int capacity;
public LinkedHashMapLRUSample(){}
public LinkedHashMapLRUSample(int capacity){
super(16, 0.75f, true);
this.capacity = capacity;
} @Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return this.size() > capacity;
} public static void main(String[] args) {
LinkedHashMapLRUSample<Integer, Integer> sample = new LinkedHashMapLRUSample<Integer, Integer>(2);
Assert.check(sample.get(2) == null);
sample.put(2,1);
sample.put(2, 2);
Assert.check(sample.get(2).intValue() == 2);
sample.put(1, 3);
Assert.check(sample.get(1) == 3);
Assert.check(sample.get(2).intValue() == 2); sample.put(3, 3);
Assert.check(sample.get(1) == null);
}
}

参考

缓存算法和实现 : 对各种缓存算法做了说明。

LRU算法机器变种

LRU Cache java实现的更多相关文章

  1. LeetCode – LRU Cache (Java)

    Problem Design and implement a data structure for Least Recently Used (LRU) cache. It should support ...

  2. leetcode 146. LRU Cache ----- java

    esign and implement a data structure for Least Recently Used (LRU) cache. It should support the foll ...

  3. lru cache java

    http://www.acmerblog.com/leetcode-lru-cache-lru-5745.html acm之家的讲解是在是好,丰富 import java.util.LinkedHas ...

  4. Java for LeetCode 146 LRU Cache 【HARD】

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  5. LRU Cache leetcode java

    题目: Design and implement a data structure for Least Recently Used (LRU) cache. It should support the ...

  6. java基于Hash表和双向链表简单实现LRU Cache

    package lru; import java.util.HashMap; public class LRUCache2<K,V> { public final int capacity ...

  7. LeetCode——LRU Cache

    Description: Design and implement a data structure for Least Recently Used (LRU) cache. It should su ...

  8. LeetCode之LRU Cache 最近最少使用算法 缓存设计

    设计并实现最近最久未使用(Least Recently Used)缓存. 题目描述: Design and implement a data structure for Least Recently ...

  9. 146. LRU Cache

    题目: Design and implement a data structure for Least Recently Used (LRU) cache. It should support the ...

随机推荐

  1. 2005: [Noi2010]能量采集

    2005: [Noi2010]能量采集 Time Limit: 10 Sec  Memory Limit: 552 MBSubmit: 1831  Solved: 1086[Submit][Statu ...

  2. Golang版protobuf编译

    官方网址: https://developers.google.com/protocol-buffers/ (需要FQ) 代码仓库: https://github.com/google/protobu ...

  3. java利用“映射文件访问”(MapperByteBuffer)处理文件与单纯利用Buffer来处理文件的快慢比较

    处理文件是java经常使用的操作,在对一个“大文件”(比如超过64M)进行操作时一点点速度的提高都会带来性能的巨大提升.然而我们经常使用的BufferxxStream,来直接处理大文件时,往往力不从心 ...

  4. MySQL查询语句的45道练习

              一.设有一数据库,包括四个表:学生表(Student).课程表(Course).成绩表(Score)以及教师信息表(Teacher).四个表的结构分别如表1-1的表(一)~表(四) ...

  5. UML软件方法大纲

    利用周末的时间读了潘加宇的<软件方法(上)>,希望梳理清楚UML的知识脉络: 工作流 子流程 内容 备注 建模和uml   利润=需求-设计   愿景   缺乏清晰.共享的愿景往往是项目失 ...

  6. 小故事理解TCP/IP连接时的三次握手

    在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接,示意图如下: 下面通过一个小故事简单理解一下这三次握手的具体含义: 一天,快递员小客(客户端)准备去小服(服务器)家去送快递(准备与服务 ...

  7. golang http server分析(一)

    http:http请求过程实质上是一个tcp连接通信,具体通过socket接口编码实现 在go中是通过listenAndServer()方法对外提供了一个http服务,在该方法中完成了socket的通 ...

  8. SVN操作手册(part1&part2)——SVN安装

    SVN操作手册 1.关于SVN 有一个简单但不十分精确比喻: SVN = 版本控制 + 备份服务器 简单的说,您可以把SVN当成您的备份服务器,更好的是,他可以帮您记住每次上传到这个服务器的档案内容. ...

  9. javascript 将数字(金额)转成大写

    将计算好的金额转换成大写,这些功能非常多,下面我改进了一下代码(原文在这里:http://www.cnblogs.com/zsanhong/p/3509464.html). /** * _SetNum ...

  10. 微信小程序已经开放个人开发者申请了,还不快上车?

    前言 就在昨天(3月27号),微信公众号平台推送了文章"小程序新能力",这篇文章是广大开发者的福音.个人开发者可申请小程序!!! 小程序开放个人开发者申请注册,个人用户可访问微信公 ...