我想实现一个简单的内存中LRU缓存系统,我正在考虑一个基于IDictionary实现的解决方案,它可以处理一个散列的LRU机制。
来自java,我有LinkedHashMap
的经验,它可以满足我的需求:我找不到类似的.NET解决方案。
有没有人开发过它或有没有人有这样的经历?
答案 0 :(得分:41)
这是我们为我们拥有的网站开发的一个非常简单的快速实现。
我们尽可能地改进代码但保持线程安全。 我认为代码非常简单明了,但如果您需要一些解释或与如何使用相关的指南,请不要犹豫。
namespace LRUCache
{
public class LRUCache<K,V>
{
private int capacity;
private Dictionary<K, LinkedListNode<LRUCacheItem<K, V>>> cacheMap = new Dictionary<K, LinkedListNode<LRUCacheItem<K, V>>>();
private LinkedList<LRUCacheItem<K, V>> lruList = new LinkedList<LRUCacheItem<K, V>>();
public LRUCache(int capacity)
{
this.capacity = capacity;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public V get(K key)
{
LinkedListNode<LRUCacheItem<K, V>> node;
if (cacheMap.TryGetValue(key, out node))
{
V value = node.Value.value;
lruList.Remove(node);
lruList.AddLast(node);
return value;
}
return default(V);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void add(K key, V val)
{
if (cacheMap.Count >= capacity)
{
RemoveFirst();
}
LRUCacheItem<K, V> cacheItem = new LRUCacheItem<K, V>(key, val);
LinkedListNode<LRUCacheItem<K, V>> node = new LinkedListNode<LRUCacheItem<K, V>>(cacheItem);
lruList.AddLast(node);
cacheMap.Add(key, node);
}
private void RemoveFirst()
{
// Remove from LRUPriority
LinkedListNode<LRUCacheItem<K,V>> node = lruList.First;
lruList.RemoveFirst();
// Remove from cache
cacheMap.Remove(node.Value.key);
}
}
class LRUCacheItem<K,V>
{
public LRUCacheItem(K k, V v)
{
key = k;
value = v;
}
public K key;
public V value;
}
}
答案 1 :(得分:7)
答案 2 :(得分:5)
发现你在google搜索时回答,也发现了这个:
http://code.google.com/p/csharp-lru-cache/
csharp-lru-cache:LRU缓存集合类库
这是一个集合类 作为最近最少使用的功能 缓存。它实现了
ICollection<T>
, 但也暴露了其他三个成员:
Capacity
,最大项目数 缓存可以包含。一旦 收集是在容量,添加一个 新项目到缓存将导致 最近最少使用的项目 丢弃。如果Capacity设置为0 在建设中,缓存不会 自动丢弃物品。Oldest
, 最古老的(即最近最少使用的) 集合中的项目。DiscardingOldestItem
,提出了一个事件 当缓存即将丢弃它时 最古老的项目这是一个极端的 简单的实施。虽然它的添加 和Remove方法是线程安全的,它 不应该用在沉重的 多线程环境因为 整个系列在锁定期间被锁定 那些方法。
答案 3 :(得分:4)
我最近发布了一个名为LurchTable的类,以满足对LinkedHashMap的C#变体的需求。简要讨论LurchTable can be found here。
基本功能:
源代码:http://csharptest.net/browse/src/Library/Collections/LurchTable.cs
GitHub:https://github.com/csharptest/CSharpTest.Net.Collections
HTML帮助:http://help.csharptest.net/
PM&GT;安装包CSharpTest.Net.Collections
答案 4 :(得分:4)
这会使Martin代码带有Mr T的建议,并使其成为Stylecop友好的代码。哦,它还允许在循环退出缓存时处理值。
namespace LruCache
{
using System;
using System.Collections.Generic;
/// <summary>
/// A least-recently-used cache stored like a dictionary.
/// </summary>
/// <typeparam name="TKey">
/// The type of the key to the cached item
/// </typeparam>
/// <typeparam name="TValue">
/// The type of the cached item.
/// </typeparam>
/// <remarks>
/// Derived from https://stackoverflow.com/a/3719378/240845
/// </remarks>
public class LruCache<TKey, TValue>
{
private readonly Dictionary<TKey, LinkedListNode<LruCacheItem>> cacheMap =
new Dictionary<TKey, LinkedListNode<LruCacheItem>>();
private readonly LinkedList<LruCacheItem> lruList =
new LinkedList<LruCacheItem>();
private readonly Action<TValue> dispose;
/// <summary>
/// Initializes a new instance of the <see cref="LruCache{TKey, TValue}"/>
/// class.
/// </summary>
/// <param name="capacity">
/// Maximum number of elements to cache.
/// </param>
/// <param name="dispose">
/// When elements cycle out of the cache, disposes them. May be null.
/// </param>
public LruCache(int capacity, Action<TValue> dispose = null)
{
this.Capacity = capacity;
this.dispose = dispose;
}
/// <summary>
/// Gets the capacity of the cache.
/// </summary>
public int Capacity { get; }
/// <summary>Gets the value associated with the specified key.</summary>
/// <param name="key">
/// The key of the value to get.
/// </param>
/// <param name="value">
/// When this method returns, contains the value associated with the specified
/// key, if the key is found; otherwise, the default value for the type of the
/// <paramref name="value" /> parameter. This parameter is passed
/// uninitialized.
/// </param>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.Dictionary`2" />
/// contains an element with the specified key; otherwise, false.
/// </returns>
public bool TryGetValue(TKey key, out TValue value)
{
lock (this.cacheMap)
{
LinkedListNode<LruCacheItem> node;
if (this.cacheMap.TryGetValue(key, out node))
{
value = node.Value.Value;
this.lruList.Remove(node);
this.lruList.AddLast(node);
return true;
}
value = default(TValue);
return false;
}
}
/// <summary>
/// Looks for a value for the matching <paramref name="key"/>. If not found,
/// calls <paramref name="valueGenerator"/> to retrieve the value and add it to
/// the cache.
/// </summary>
/// <param name="key">
/// The key of the value to look up.
/// </param>
/// <param name="valueGenerator">
/// Generates a value if one isn't found.
/// </param>
/// <returns>
/// The requested value.
/// </returns>
public TValue Get(TKey key, Func<TValue> valueGenerator)
{
lock (this.cacheMap)
{
LinkedListNode<LruCacheItem> node;
TValue value;
if (this.cacheMap.TryGetValue(key, out node))
{
value = node.Value.Value;
this.lruList.Remove(node);
this.lruList.AddLast(node);
}
else
{
value = valueGenerator();
if (this.cacheMap.Count >= this.Capacity)
{
this.RemoveFirst();
}
LruCacheItem cacheItem = new LruCacheItem(key, value);
node = new LinkedListNode<LruCacheItem>(cacheItem);
this.lruList.AddLast(node);
this.cacheMap.Add(key, node);
}
return value;
}
}
/// <summary>
/// Adds the specified key and value to the dictionary.
/// </summary>
/// <param name="key">
/// The key of the element to add.
/// </param>
/// <param name="value">
/// The value of the element to add. The value can be null for reference types.
/// </param>
public void Add(TKey key, TValue value)
{
lock (this.cacheMap)
{
if (this.cacheMap.Count >= this.Capacity)
{
this.RemoveFirst();
}
LruCacheItem cacheItem = new LruCacheItem(key, value);
LinkedListNode<LruCacheItem> node =
new LinkedListNode<LruCacheItem>(cacheItem);
this.lruList.AddLast(node);
this.cacheMap.Add(key, node);
}
}
private void RemoveFirst()
{
// Remove from LRUPriority
LinkedListNode<LruCacheItem> node = this.lruList.First;
this.lruList.RemoveFirst();
// Remove from cache
this.cacheMap.Remove(node.Value.Key);
// dispose
this.dispose?.Invoke(node.Value.Value);
}
private class LruCacheItem
{
public LruCacheItem(TKey k, TValue v)
{
this.Key = k;
this.Value = v;
}
public TKey Key { get; }
public TValue Value { get; }
}
}
}
答案 5 :(得分:3)
EntLib的Caching Application Block具有开箱即用的LRU清理选项,可以在内存中。对于你想要的东西,它可能有点重量级。
答案 6 :(得分:2)
我不相信。我当然看到在各种不相关的项目中实施了多次手工轧制(这或多或少证实了这一点。如果有的话,肯定至少有一个项目会使用它。)
实现起来非常简单,通常可以通过创建包含Dictionary
和List
的类来完成。
按键进入列表(按顺序),项目进入字典 当您向集合中添加新项时,该函数会检查列表的长度,拉出最后一个键(如果它太长),然后从字典中删除键和值以匹配。真的没那么多了
答案 7 :(得分:2)
我喜欢劳伦斯的实施。 Hashtable + LinkedList是一个很好的解决方案。 关于线程,我不会锁定[MethodImpl(MethodImplOptions.Synchronized)],而是使用ReaderWriterLockSlim或自旋锁(因为争用通常很快)。 在get函数中,我会检查它是否已经是第一个项目,而不是总是删除和添加。这使您可以保持读卡器锁定,而不会阻止其他读者。
答案 8 :(得分:2)
我实现了一个为并发工作负载设计的线程安全伪LRU。性能非常接近ConcurrentDictionary,比MemoryCache快10倍左右,命中率也比常规LRU好。下面的github链接中提供了完整的分析。
用法如下:
int capacity = 666;
var lru = new ConcurrentLru<int, SomeItem>(capacity);
var value = lru.GetOrAdd(1, (k) => new SomeItem(k));
GitHub:https://github.com/bitfaster/BitFaster.Caching
Install-Package BitFaster.Caching
答案 9 :(得分:0)
如果它是一个asp.net应用程序,你可以使用缓存类[1],但你将与其他缓存的东西竞争空间,这可能是你想要的或不是。
[1] http://msdn.microsoft.com/en-us/library/system.web.caching.cache.aspx
答案 10 :(得分:0)
我自己创建了一个LRU缓存nuget包。这是该链接:https://www.nuget.org/packages/jakuma.LRUCache/
LRU(最近最少使用)缓存的简单轻量级线程安全实现。
用法:
LRUCache<int, int> lruCache = new LRUCache<int, int>(3);
lruCache.Set(1, 1);
lruCache.Set(2, 2);
lruCache.Set(3, 3);
lruCache.Get(1);
lruCache.Get(2);
lruCache.Get(3);
lruCache.Set(4, 4);
lruCache.Delete(2);
lruCache.Set(4, 5);
lruCache.DeleteAll();
答案 11 :(得分:0)
我已经创建了LRU缓存的实现
https://github.com/mohsenShakiba/LRUCache
答案 12 :(得分:0)
我现在偶然在 aws-sdk-net 中发现了 LruCache.cs:https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/Internal/Util/LruCache.cs