C# 是否有IDictionary的LRU实现?

C# 是否有IDictionary的LRU实现?,c#,programming-languages,caching,C#,Programming Languages,Caching,我想实现一个简单的内存中LRU缓存系统,我正在考虑一个基于IDictionary实现的解决方案,它可以处理哈希LRU机制。 来自java,我有使用LinkedHashMap的经验,它可以很好地满足我的需要:我在任何地方都找不到类似的.NET解决方案 有人开发过它吗?或者有人有过类似的经历吗?基类库中没有任何东西可以做到这一点 在自由端,也许像C5这样的东西会起作用 如果您愿意付费,也许可以查看它包含的实现。我不这么认为。我肯定见过在各种不相关的项目中多次实施手工操作(这或多或少证实了这一点。如果

我想实现一个简单的内存中LRU缓存系统,我正在考虑一个基于IDictionary实现的解决方案,它可以处理哈希LRU机制。 来自java,我有使用
LinkedHashMap
的经验,它可以很好地满足我的需要:我在任何地方都找不到类似的.NET解决方案


有人开发过它吗?或者有人有过类似的经历吗?

基类库中没有任何东西可以做到这一点

在自由端,也许像C5这样的东西会起作用


如果您愿意付费,也许可以查看它包含的实现。

我不这么认为。我肯定见过在各种不相关的项目中多次实施手工操作(这或多或少证实了这一点。如果有,肯定至少有一个项目会使用它)

它的实现非常简单,通常通过创建一个包含
字典
列表
的类来实现

键(按顺序)进入列表,项进入字典。

向集合中添加新项时,该函数将检查列表的长度,拉出最后一个键(如果太长),然后从字典中取出键和值以进行匹配。事实上,EntLib有一个现成的LRU清除选项,可以放在内存中。对于你想要的东西,它可能有点重。

如果它是asp.net应用程序,你可以使用缓存类[1],但你将与其他缓存的东西争夺空间,这些东西可能是你想要的,也可能不是


[1] 这是我们为自己的网站开发的一个非常简单、快速的实现

我们尽量改进代码,同时保持线程安全。 我认为代码非常简单明了,但是如果您需要一些关于如何使用它的解释或指南,请不要犹豫询问

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;
    }
}
名称空间LRUCache
{
公共类LRUCache
{
私人int能力;
private Dictionary cacheMap=新字典();
private LinkedList lruList=new LinkedList();
公共LRUCache(内部容量)
{
这个。容量=容量;
}
[MethodImpl(MethodImplOptions.Synchronized)]
公共V get(K键)
{
LinkedListNode节点;
if(cacheMap.TryGetValue(键,out节点))
{
V值=node.value.value;
移除(节点);
lruList.AddLast(节点);
返回值;
}
返回默认值(V);
}
[MethodImpl(MethodImplOptions.Synchronized)]
公共无效添加(K键,V值)
{
如果(cacheMap.Count>=容量)
{
移除第一个();
}
LRUCacheItem cacheItem=新的LRUCacheItem(键,val);
LinkedListNode节点=新的LinkedListNode(cacheItem);
lruList.AddLast(节点);
添加(键,节点);
}
私有void RemoveFirst()
{
//除名
LinkedListNode节点=lruList.First;
lruList.RemoveFirst();
//从缓存中删除
cacheMap.Remove(node.Value.key);
}
}
类LRUCacheItem
{
公共物品(K、V、V)
{
key=k;
值=v;
}
公钥;
公共价值观;
}
}

我喜欢劳伦斯的实现。Hashtable+LinkedList是一个很好的解决方案

关于线程,我不会用
[MethodImpl(MethodImplOptions.Synchronized)]
锁定它,而是使用
readerwriterlocksim
或自旋锁(因为争用通常很快)


Get
函数中,我会首先检查它是否已经是第一项,而不是总是删除和添加。这使您可以将其保存在一个读卡器锁内,而不会阻止其他读卡器。

在谷歌搜索时发现您的答案,还发现:

:LRU缓存集合类库

这是一个集合类 作为最近使用最少的 隐藏物它实现了
i收集
, 但也暴露了其他三名成员:

  • 容量
    ,最大项目数 缓存可以包含。一旦 集合已满,正在添加 缓存中的新项将导致 最近最少使用的项目 丢弃的。如果容量设置为0 在构造时,缓存将不会 自动丢弃项目
  • 最早的
    , 最老的(即最近使用最少的) 收藏中的项目
  • DiscardingOldestItem
    ,引发了一个事件 当缓存即将丢弃其 最古老的物品。这是一个极其重要的问题 简单的实现。当它添加 而且删除方法是线程安全的,它 不应该用在重型车辆上 多线程环境,因为 整个集合在运行期间被锁定 这些方法

我最近发布了一个名为LurchTable的类,以满足对LinkedHashMap的C#变体的需求。对这一问题的简要讨论

基本特征:

  • 通过插入、修改或访问链接的并发字典
  • Dictionary/ConcurrentDictionary接口支持
  • Peek/TryDequeue/Dequeue访问“最旧”条目
  • 允许对插入时强制执行的项目进行硬限制
  • 公开用于添加、更新和删除的事件
源代码:

GitHub:

HTML帮助:

PM>安装软件包

这会将的代码与的建议结合起来,并使其对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; }
        }
    }
}
名称空间LruCache
{
使用制度;
使用System.Collections.Generic;
/// 
///像dic一样存储的最近使用最少的缓存
int capacity = 666;
var lru = new ConcurrentLru<int, SomeItem>(capacity);

var value = lru.GetOrAdd(1, (k) => new SomeItem(k));
Install-Package BitFaster.Caching