我正在研究LRU缓存实现的这个问题,其中在缓存大小已满后,弹出最近最少使用的项目,并将其替换为新项目。
我有两个实现:
1)。创建两个看起来像这样的地图
std::map<timestamp, k> time_to_key
std::map<key, std::pair<timestamp, V>> LRUCache
要插入新元素,我们可以将当前时间戳和值放在 LRUCache 中。当缓存大小已满时,我们可以通过查找 time _to_ key 中存在的最小时间戳并从中删除相应的密钥来逐出最新元素。 LRUCache 。 插入一个新项是O(1),更新时间戳是O(n)(因为我们需要在时间 _to_ 中搜索与时间戳相对应的 k 键
2)。有一个链表,其中最近最少使用的缓存出现在头部,新项目在尾部添加。当项目到达时已经存在于高速缓存中,与该项目的键对应的节点被移动到列表的尾部。 插入一个新项是O(1),更新时间戳再次是O(n)(因为我们需要移动到列表的尾部),删除一个元素是O(1)。
现在我有以下问题:
对于LRUCache,这些实现中哪一个更好。
是否有其他方法可以实现LRU缓存。
在Java中,我应该使用HashMap来实现LRUCache
我见过类似的问题,实现了一个通用的LRU缓存,并且还遇到了实现LRU缓存等问题。通用LRU缓存是否与LRU缓存不同?
提前致谢!!!
修改
在Java中实现LRUCache的另一种方法(最简单的方法)是使用LinkedHashMap并重写boolean removeEldestEntry(Map.entry eldest)函数。
答案 0 :(得分:25)
如果你想要一个LRU缓存,Java中最简单的就是LinkedHashMap。默认行为是FIFO,但您可以将其更改为“访问顺序”,这使其成为LRU缓存。
public static <K,V> Map<K,V> lruCache(final int maxSize) {
return new LinkedHashMap<K, V>(maxSize*4/3, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
};
}
注意:我使用了constructor,它将集合从最新的第一个更改为最近使用的第一个。
来自Javadoc
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder)
Constructs an empty LinkedHashMap instance with the specified initial capacity, load factor and ordering mode.
Parameters:
initialCapacity - the initial capacity
loadFactor - the load factor
accessOrder - the ordering mode - true for access-order, false for insertion-order
当accessOrder为true
时,只要你得到一个不是最后一个条目的条目,LinkedHashMap就会重新排列地图的顺序。
这样最旧的条目是最近使用的条目。
答案 1 :(得分:2)
通常,LRU高速缓存表示为LIFO结构 - 单个元素队列。如果您的标准提供的那个不允许您从中间移除对象,例如,将它们放在顶部,那么您可能必须自己滚动。
答案 2 :(得分:2)
考虑到缓存允许并发访问,LRU缓存的良好设计问题可归结为:
a)我们可以在更新两个结构(缓存结构和LRU结构)时避免使用互斥锁。
b)缓存的读取(get)操作是否需要互斥?
更详细:比如说,如果我们使用java.util.concurrent.ConcuurentHashMap(缓存结构)和java.util.concurrent.ConcurrentLinkedQueue(LRU结构)来实现它
a)在编辑操作中锁定这两个结构 - addEntry(),removeEntry(),evictEntries()等。
b)以上内容可能会因为写入操作速度慢而传递,但问题甚至是读取(get)操作,我们需要在两个结构上应用锁定。因为,get意味着将条目放在LRU策略的队列前面。(假设条目从队列末尾删除)。
使用高效的并发结构(如ConcurrentHashMap)并等待自由的ConcurrentLinkedQueue然后锁定它们就会失去它的全部目的。
我使用相同的方法实现了LRU缓存,但LRU结构是异步更新的,无需在访问这些结构时使用任何互斥锁。 LRU是Cache的内部细节,可以以任何方式实现,而不会影响缓存的用户。
稍后,我还阅读了ConcurrentLinkedHashMap
https://code.google.com/p/concurrentlinkedhashmap/
并发现它也在尝试做同样的事情。没有使用这种结构,但可能是一个很好的选择。
答案 3 :(得分:2)
我想通过两种可能的实现来扩展其中一些建议。一个不是线程安全的,可能是一个。
这是一个最简单的版本,带有单元测试,可以显示它的工作原理。
首先是非并发版本:
import java.util.LinkedHashMap;
import java.util.Map;
public class LruSimpleCache<K, V> implements LruCache <K, V>{
Map<K, V> map = new LinkedHashMap ( );
public LruSimpleCache (final int limit) {
map = new LinkedHashMap <K, V> (16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
return super.size() > limit;
}
};
}
@Override
public void put ( K key, V value ) {
map.put ( key, value );
}
@Override
public V get ( K key ) {
return map.get(key);
}
//For testing only
@Override
public V getSilent ( K key ) {
V value = map.get ( key );
if (value!=null) {
map.remove ( key );
map.put(key, value);
}
return value;
}
@Override
public void remove ( K key ) {
map.remove ( key );
}
@Override
public int size () {
return map.size ();
}
public String toString() {
return map.toString ();
}
}
真正的旗帜将跟踪获取和放置的访问。请参阅JavaDocs。没有构造函数的true标志的removeEdelstEntry只会实现FIFO缓存(请参阅下面关于FIFO和removeEldestEntry的注释)。
以下测试证明它可以作为LRU缓存使用:
public class LruSimpleTest {
@Test
public void test () {
LruCache <Integer, Integer> cache = new LruSimpleCache<> ( 4 );
cache.put ( 0, 0 );
cache.put ( 1, 1 );
cache.put ( 2, 2 );
cache.put ( 3, 3 );
boolean ok = cache.size () == 4 || die ( "size" + cache.size () );
cache.put ( 4, 4 );
cache.put ( 5, 5 );
ok |= cache.size () == 4 || die ( "size" + cache.size () );
ok |= cache.getSilent ( 2 ) == 2 || die ();
ok |= cache.getSilent ( 3 ) == 3 || die ();
ok |= cache.getSilent ( 4 ) == 4 || die ();
ok |= cache.getSilent ( 5 ) == 5 || die ();
cache.get ( 2 );
cache.get ( 3 );
cache.put ( 6, 6 );
cache.put ( 7, 7 );
ok |= cache.size () == 4 || die ( "size" + cache.size () );
ok |= cache.getSilent ( 2 ) == 2 || die ();
ok |= cache.getSilent ( 3 ) == 3 || die ();
ok |= cache.getSilent ( 4 ) == null || die ();
ok |= cache.getSilent ( 5 ) == null || die ();
if ( !ok ) die ();
}
现在为并发版本......
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LruSimpleConcurrentCache<K, V> implements LruCache<K, V> {
final CacheMap<K, V>[] cacheRegions;
private static class CacheMap<K, V> extends LinkedHashMap<K, V> {
private final ReadWriteLock readWriteLock;
private final int limit;
CacheMap ( final int limit, boolean fair ) {
super ( 16, 0.75f, true );
this.limit = limit;
readWriteLock = new ReentrantReadWriteLock ( fair );
}
protected boolean removeEldestEntry ( final Map.Entry<K, V> eldest ) {
return super.size () > limit;
}
@Override
public V put ( K key, V value ) {
readWriteLock.writeLock ().lock ();
V old;
try {
old = super.put ( key, value );
} finally {
readWriteLock.writeLock ().unlock ();
}
return old;
}
@Override
public V get ( Object key ) {
readWriteLock.writeLock ().lock ();
V value;
try {
value = super.get ( key );
} finally {
readWriteLock.writeLock ().unlock ();
}
return value;
}
@Override
public V remove ( Object key ) {
readWriteLock.writeLock ().lock ();
V value;
try {
value = super.remove ( key );
} finally {
readWriteLock.writeLock ().unlock ();
}
return value;
}
public V getSilent ( K key ) {
readWriteLock.writeLock ().lock ();
V value;
try {
value = this.get ( key );
if ( value != null ) {
this.remove ( key );
this.put ( key, value );
}
} finally {
readWriteLock.writeLock ().unlock ();
}
return value;
}
public int size () {
readWriteLock.readLock ().lock ();
int size = -1;
try {
size = super.size ();
} finally {
readWriteLock.readLock ().unlock ();
}
return size;
}
public String toString () {
readWriteLock.readLock ().lock ();
String str;
try {
str = super.toString ();
} finally {
readWriteLock.readLock ().unlock ();
}
return str;
}
}
public LruSimpleConcurrentCache ( final int limit, boolean fair ) {
int cores = Runtime.getRuntime ().availableProcessors ();
int stripeSize = cores < 2 ? 4 : cores * 2;
cacheRegions = new CacheMap[ stripeSize ];
for ( int index = 0; index < cacheRegions.length; index++ ) {
cacheRegions[ index ] = new CacheMap<> ( limit / cacheRegions.length, fair );
}
}
public LruSimpleConcurrentCache ( final int concurrency, final int limit, boolean fair ) {
cacheRegions = new CacheMap[ concurrency ];
for ( int index = 0; index < cacheRegions.length; index++ ) {
cacheRegions[ index ] = new CacheMap<> ( limit / cacheRegions.length, fair );
}
}
private int stripeIndex ( K key ) {
int hashCode = key.hashCode () * 31;
return hashCode % ( cacheRegions.length );
}
private CacheMap<K, V> map ( K key ) {
return cacheRegions[ stripeIndex ( key ) ];
}
@Override
public void put ( K key, V value ) {
map ( key ).put ( key, value );
}
@Override
public V get ( K key ) {
return map ( key ).get ( key );
}
//For testing only
@Override
public V getSilent ( K key ) {
return map ( key ).getSilent ( key );
}
@Override
public void remove ( K key ) {
map ( key ).remove ( key );
}
@Override
public int size () {
int size = 0;
for ( CacheMap<K, V> cache : cacheRegions ) {
size += cache.size ();
}
return size;
}
public String toString () {
StringBuilder builder = new StringBuilder ();
for ( CacheMap<K, V> cache : cacheRegions ) {
builder.append ( cache.toString () ).append ( '\n' );
}
return builder.toString ();
}
}
您可以看到为什么我首先覆盖非并发版本。以上尝试创建一些条带以减少锁争用。所以我们哈希键,然后查找该哈希以找到实际的缓存。这使得限制大小更多地是在相当大的误差范围内的建议/粗略猜测,具体取决于密钥散列算法的传播程度。
答案 4 :(得分:1)
问题陈述:
创建LRU缓存并存储Employee对象Max = 5个对象,并找出谁首先登录,然后找到....
package com.test.example.dto;
import java.sql.Timestamp;
/**
*
* @author Vaquar.khan@gmail.com
*
*/
public class Employee implements Comparable<Employee> {
private int id;
private String name;
private int age;
private Timestamp loginTime ;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Timestamp getLoginTime() {
return loginTime;
}
public void setLoginTime(Timestamp loginTime) {
this.loginTime = loginTime;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", age=" + age + ", loginTime=" + loginTime + "]";
}
Employee(){}
public Employee(int id, String name, int age, Timestamp loginTime) {
super();
this.id = id;
this.name = name;
this.age = age;
this.loginTime = loginTime;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + id;
result = prime * result + ((loginTime == null) ? 0 : loginTime.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Employee other = (Employee) obj;
if (age != other.age) return false;
if (id != other.id) return false;
if (loginTime == null) {
if (other.loginTime != null) return false;
} else if (!loginTime.equals(other.loginTime)) return false;
if (name == null) {
if (other.name != null) return false;
} else if (!name.equals(other.name)) return false;
return true;
}
@Override
public int compareTo(Employee emp) {
if (emp.getLoginTime().before( this.loginTime) ){
return 1;
} else if (emp.getLoginTime().after(this.loginTime)) {
return -1;
}
return 0;
}
}
LRUObjectCacheExample
package com.test.example;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.test.example.dto.Employee;
/**
*
* @author Vaquar.khan@gmail.com
*
*/
public class LRUObjectCacheExample {
LinkedHashMap<Employee, Boolean> lruCacheLinkedQueue;
public LRUObjectCacheExample(int capacity) {
lruCacheLinkedQueue = new LinkedHashMap<Employee, Boolean>(capacity, 1.0f, true) {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(
//calling map's entry method
Map.Entry<Employee, Boolean> eldest) {
return this.size() > capacity;
}
};
}
void addDataIntoCache(Employee employee) {
lruCacheLinkedQueue.put(employee, true);
displayLRUQueue();
}
boolean checkIfDataPresentIntoLRUCaache(int data) {
return lruCacheLinkedQueue.get(data) != null;
}
void deletePageNo(int data) {
if (lruCacheLinkedQueue.get(data) != null){
lruCacheLinkedQueue.remove(data);
}
displayLRUQueue();
}
void displayLRUQueue() {
System.out.print("-------------------------------------------------------"+"\n");
System.out.print("Data into LRU Cache : ");
for (Entry<Employee, Boolean> mapEntry : lruCacheLinkedQueue.entrySet()) {
System.out.print("[" + mapEntry.getKey() + "]");
}
System.out.println("");
}
public static void main(String args[]) {
Employee employee1 = new Employee(1,"Shahbaz",29, getCurrentTimeStamp());
Employee employee2 = new Employee(2,"Amit",35,getCurrentTimeStamp());
Employee employee3 = new Employee(3,"viquar",36,getCurrentTimeStamp());
Employee employee4 = new Employee(4,"Sunny",20,getCurrentTimeStamp());
Employee employee5 = new Employee(5,"sachin",28,getCurrentTimeStamp());
Employee employee6 = new Employee(6,"Sneha",25,getCurrentTimeStamp());
Employee employee7 = new Employee(7,"chantan",19,getCurrentTimeStamp());
Employee employee8 = new Employee(8,"nitin",22,getCurrentTimeStamp());
Employee employee9 = new Employee(9,"sanuj",31,getCurrentTimeStamp());
//
LRUObjectCacheExample lru = new LRUObjectCacheExample(5);
lru.addDataIntoCache(employee5);//sachin
lru.addDataIntoCache(employee4);//Sunny
lru.addDataIntoCache(employee3);//viquar
lru.addDataIntoCache(employee2);//Amit
lru.addDataIntoCache(employee1);//Shahbaz -----capacity reached
//
lru.addDataIntoCache(employee6);/Sneha
lru.addDataIntoCache(employee7);//chantan
lru.addDataIntoCache(employee8);//nitin
lru.addDataIntoCache(employee9);//sanuj
//
lru.deletePageNo(3);
lru.deletePageNo(4);
}
private static Timestamp getCurrentTimeStamp(){
return new java.sql.Timestamp(Calendar.getInstance().getTime().getTime());
}
}
<强>结果:强>
**Data into LRU Cache :**
[Employee [id=1, name=Shahbaz, age=29, loginTime=2015-10-15 18:47:28.1
[Employee [id=6, name=Sneha, age=25, loginTime=2015-10-15 18:47:28.125
[Employee [id=7, name=chantan, age=19, loginTime=2015-10-15 18:47:28.125
[Employee [id=8, name=nitin, age=22, loginTime=2015-10-15 18:47:28.125
[Employee [id=9, name=sanuj, age=31, loginTime=2015-10-15 18:47:28.125
答案 5 :(得分:1)
LinkedHashMap允许您覆盖removeEldestEntry函数,这样无论何时执行put,您都可以指定是否删除最旧的条目,从而允许实现LRU。
myLRUCache = new LinkedHashMap<Long,String>() {
protected boolean removeEldestEntry(Map.Entry eldest)
{
if(this.size()>1000)
return true;
else
return false;
}
};
答案 6 :(得分:0)
对于O(1)访问,我们需要一个哈希表并维护顺序,我们可以使用DLL。 基本算法是 - 从页码我们可以使用哈希表到达DLL节点。如果页面存在,我们可以将节点移动到DLL的头部,否则在DLL和散列表中插入节点。如果DLL的大小已满,我们可以从尾部删除最近最少使用的节点。
这是一个基于双向链表和C ++中unordered_map的实现。
#include <iostream>
#include <unordered_map>
#include <utility>
using namespace std;
// List nodeclass
class Node
{
public:
int data;
Node* next;
Node* prev;
};
//Doubly Linked list
class DLList
{
public:
DLList()
{
head = NULL;
tail = NULL;
count = 0;
}
~DLList() {}
Node* addNode(int val);
void print();
void removeTail();
void moveToHead(Node* node);
int size()
{
return count;
}
private:
Node* head;
Node* tail;
int count;
};
// Function to add a node to the list
Node* DLList::addNode(int val)
{
Node* temp = new Node();
temp->data = val;
temp->next = NULL;
temp->prev = NULL;
if ( tail == NULL )
{
tail = temp;
head = temp;
}
else
{
head->prev = temp;
temp->next = head;
head = temp;
}
count++;
return temp;
}
void DLList::moveToHead(Node* node)
{
if (head == node)
return;
node->prev->next = node->next;
if (node->next != NULL)
{
node->next->prev = node->prev;
}
else
{
tail = node->prev;
}
node->next = head;
node->prev = NULL;
head->prev = node;
head = node;
}
void DLList::removeTail()
{
count--;
if (head == tail)
{
delete head;
head = NULL;
tail = NULL;
}
else
{
Node* del = tail;
tail = del->prev;
tail->next = NULL;
delete del;
}
}
void DLList::print()
{
Node* temp = head;
int ctr = 0;
while ( (temp != NULL) && (ctr++ != 25) )
{
cout << temp->data << " ";
temp = temp->next;
}
cout << endl;
}
class LRUCache
{
public:
LRUCache(int aCacheSize);
void fetchPage(int pageNumber);
private:
int cacheSize;
DLList dlist;
unordered_map< int, Node* > directAccess;
};
LRUCache::LRUCache(int aCacheSize):cacheSize(aCacheSize) { }
void LRUCache::fetchPage(int pageNumber)
{
unordered_map< int, Node* >::const_iterator it = directAccess.find(pageNumber);
if (it != directAccess.end())
{
dlist.moveToHead( (Node*)it->second);
}
else
{
if (dlist.size() == cacheSize-1)
dlist.removeTail();
Node* node = dlist.addNode(pageNumber);
directAccess.insert(pair< int, Node* >(pageNumber,node));
}
dlist.print();
}
int main()
{
LRUCache lruCache(10);
lruCache.fetchPage(5);
lruCache.fetchPage(7);
lruCache.fetchPage(15);
lruCache.fetchPage(34);
lruCache.fetchPage(23);
lruCache.fetchPage(21);
lruCache.fetchPage(7);
lruCache.fetchPage(32);
lruCache.fetchPage(34);
lruCache.fetchPage(35);
lruCache.fetchPage(15);
lruCache.fetchPage(37);
lruCache.fetchPage(17);
lruCache.fetchPage(28);
lruCache.fetchPage(16);
return 0;
}
答案 7 :(得分:0)
扩展Peter Lawrey的回答
可以重写df <- structure(list(id = c(1, 1, 1, 1, 2, 2, 2, 2), year = c(1900,
1900, 1900, 1900, 1901, 1901, 1901, 1901), month = c(1L, 2L,
3L, 4L, 1L, 2L, 3L, 4L), temp = c(51.8928991815029, 52.8768994596968,
70.0998976356871, 62.2724802472936, 51.8928991815029, 52.8768994596968,
70.0998976356871, 62.2724802472936), croparea = c(50, 50, 50,
50, 30, 30, 30, 30)), .Names = c("id", "year", "month", "temp",
"croparea"), row.names = c(NA, -8L), class = "data.frame")
id year month temp croparea
1 1 1900 1 51.89290 50
2 1 1900 2 52.87690 50
3 1 1900 3 70.09990 50
4 1 1900 4 62.27248 50
5 2 1901 1 51.89290 30
6 2 1901 2 52.87690 30
7 2 1901 3 70.09990 30
8 2 1901 4 62.27248 30
方法,以便在将新映射添加到地图时自动删除过时映射的策略。
因此可以覆盖LinkedHashMap的FIFO行为以生成LRU
removeEldestEntry(java.util.Map.Entry)
答案 8 :(得分:0)
class LRU {
Map<String, Integer> ageMap = new HashMap<String, Integer>();
int age = 1;
void addElementWithAge(String element) {
ageMap.put(element, age);
age++;
}
String getLeastRecent(Map<String, Integer> ageMap) {
Integer oldestAge = (Integer) ageMap.values().toArray()[0];
String element = null;
for (Entry<String, Integer> entry : ageMap.entrySet()) {
if (oldestAge >= entry.getValue()) {
oldestAge = entry.getValue();
element = entry.getKey();
}
}
return element;
}
public static void main(String args[]) {
LRU obj = new LRU();
obj.addElementWithAge("M1");
obj.addElementWithAge("M2");
obj.addElementWithAge("M3");
obj.addElementWithAge("M4");
obj.addElementWithAge("M1");
}
}