我想实现 LRU cache
,其中最近最少使用的元素将被异步驱逐。我目前的想法是使用Dictionary
来存储<key,value>
对,并跟踪对象的访问次数,以保持SortedDictionary <key, timestamp>
。我们的想法是让异步线程从SortedDictionary
获取LRU项并从缓存中删除。但为了实现这一点,SortedDictionary
需要按值排序,而不是。
我本来可以使用单独的SortedList
而不是SortedDictionary
来保持{key和timestamp}按时间戳排序,但之后我将不得不进行“线性”查找以查找列表中的键(当我必须更新时间戳时,再次访问相同的键时) - 如果可能的话,我正在寻找比线性方式更好的方法。有人可以分享想法来处理这个问题吗?
所以,我的问题归结为:
我要在&lt; = logn time中查找密钥以更新时间戳,同时能够根据时间戳对密钥进行排序。
一种方法是保留SortedDictionary
<{key,timestamp},null>
hashcode()
,根据{key,timestamp}的时间戳部分对键进行排序。虽然这很好,但问题是equals()
只需要返回key.hashcode()(用于在更新时间戳时查找),而equals()
也应该使用时间戳。因此,hashcode()
和{{1}}存在冲突,所以觉得这不是一个好主意......
答案 0 :(得分:4)
你应该做的是保留两个词典,一个按时间排序,一个按键排序。
请记住,字典只保存对实际对象的引用,因此用于更新对象的字典无关紧要。
要更新对象,请创建一个将更新字典
的函数var oldObj = keyedObject[key];
timedObjects.Remove(oldObj.LastUpdateTime);
timedObjects.Add(myUpdatedObject.LastUpdateTime,myUpdatedObject);
keyedObject[key] = myUpdatedObject;
现在你可以通过时间和关键点跟踪同一个对象。
我只保留timedObjects
中对象的一个引用。这有助于删除。
您可以根据需要继续修剪timedObjects字典。
Ofcource,在修剪时你必须记住,还有另一个字典keyedObject
引用了同一个对象。仅仅呼叫Remove
是不够的。
您的删除代码必须如下:
removeObject = timedObjects[timeToRemove];
timedObjects.Remove(timeToRemove);
keyedObject.Remove(removeObject.key);
timeToRemove主要来自for循环,您可以在其中决定要删除的对象
答案 1 :(得分:0)
您正在寻找的地图类型(至少在Java中)称为LinkedHashMap
。
来自javadoc:
使用Hash表和Map接口的链表实现 可预测的迭代顺序。此实现与HashMap不同 因为它维护着一个贯穿所有链接的双向链表 条目。该链表定义了迭代排序,即 通常是键插入地图的顺序 (插入顺序)。
提供了一个特殊的构造函数来创建一个链接的哈希映射 迭代顺序是其条目的最后顺序 访问,从最近访问到最近访问 (存取顺序)。这种地图非常适合构建LRU 高速缓存强>
Source for LinkedHashMap
from the OpenJDK
AFAIK,C#中没有LinkedHashMap
的现有实现。话虽如此,写一个也不是非常困难。
答案 2 :(得分:0)
不是sorteddictionary,而是编写自己的链表,并让Dictionary指向其节点作为值。它总是按时间戳排序,更新时间戳并删除最少使用的元素将为O(1)。
答案 3 :(得分:0)
这是c#中LRU缓存的实现。有效的O(1),但不是线程安全的;
static void Main(string[] args)
{
var cache = new LruCache(3);
cache.Put(1, 1);
cache.Put(2, 2);
Console.WriteLine(cache.Get(1)); // returns 1
cache.Put(3, 3); // evicts key 2
Console.WriteLine(cache.Get(2)); // returns -1 (not found)
cache.Put(4, 4); // evicts key 1
Console.WriteLine(cache.Get(1)); // returns -1 (not found)
Console.WriteLine(cache.Get(3)); // returns 3
Console.WriteLine(cache.Get(4)); // returns 4
}
public class DoubleLinkedList
{
public int key;
public int value;
public DoubleLinkedList next;
public DoubleLinkedList prev;
public DoubleLinkedList(int k, int v)
{
key = k;
value = v;
}
}
public class LruCache
{
private int size;
private int capacity;
private Dictionary<int, DoubleLinkedList> map;
private DoubleLinkedList head;
private DoubleLinkedList tail;
public LruCache(int cap)
{
capacity = cap;
map = new Dictionary<int, DoubleLinkedList>();
head = new DoubleLinkedList(0, 0);
tail = new DoubleLinkedList(0, 0);
head.next = tail;
tail.prev = head;
}
public int Get(int key)
{
if (map.ContainsKey(key))
{
if (tail.prev.key != key)
{
var node = map[key];
RemoveNode(node);
AddToEnd(node);
}
return map[key].value;
}
return -1;
}
private void AddToEnd(DoubleLinkedList node)
{
var beforeTail = tail.prev;
node.prev = beforeTail;
beforeTail.next = node;
tail.prev = node;
node.next = tail;
}
private void RemoveNode(DoubleLinkedList node)
{
var before = node.prev;
before.next = node.next;
node.next.prev = before;
}
public void Put(int key, int value)
{
if (map.ContainsKey(key))
{
map[key].value = value;
var node = map[key];
RemoveNode(node);
AddToEnd(node);
}
else
{
size++;
if (size > capacity)
{
var node = head.next;
RemoveNode(node);
map.Remove(node.key);
size--;
}
var newNode = new DoubleLinkedList(key, value);
AddToEnd(newNode);
map.Add(key, newNode);
}
}
}