【问题标题】:Is it there any LRU implementation of IDictionary?IDictionary 是否有任何 LRU 实现?
【发布时间】:2010-10-19 17:56:36
【问题描述】:

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

有没有人开发过或者有过这样的经历?

【问题讨论】:

    标签: c# programming-languages caching


    【解决方案1】:

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

    在免费方面,也许像 C5 的 HashedLinkedList 这样的东西会起作用。

    如果您愿意付费,不妨查看this C# toolkit. 它包含一个实现。

    【讨论】:

    • C5 正是我想要的。谢谢... :)
    • @Antonello,链接的 c5 似乎没有按访问排序(正如 LinkedHashMap 能够做到的那样)。我是否遗漏了某些内容,或者您​​是否在访问项目时删除并重新插入?
    【解决方案2】:

    我不相信。我当然见过在各种不相关的项目中多次实施手动卷(这或多或少证实了这一点。如果有的话,肯定至少有一个项目会使用它)。

    实现起来非常简单,通常通过创建一个包含DictionaryList 的类来完成。

    键进入列表(按顺序),项目进入字典。
    当您向集合中添加新项目时,该函数会检查列表的长度,取出最后一个键(如果它太长),然后从字典中逐出键和值以进行匹配。真的不多了

    【讨论】:

    • 这是一个简单且有效的解决方案,尽管通过双重访问两个集合(首先是 List 检索键,然后是 Dictionary 检索值)会损失性能,但它正在工作.. .
    【解决方案3】:

    EntLib 的Caching Application Block 具有开箱即用的 LRU 清除选项,可以在内存中。对于您想要的东西,它可能有点重量级。

    【讨论】:

      【解决方案4】:

      如果它是一个 asp.net 应用程序,您可以使用缓存类[1],但您将与其他缓存的内容竞争空间,这可能是您想要的,也可能不是。

      [1]http://msdn.microsoft.com/en-us/library/system.web.caching.cache.aspx

      【讨论】:

        【解决方案5】:

        这是我们为我们拥有的网站开发的一个非常简单和快速的实现。

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

        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.TryGetValue(key, out var existingNode))
                    {
                        lruList.Remove(existingNode);
                    }
                    else 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); - here's bug if try to add already existing value
                    cacheMap[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;
            }
        }
        

        【讨论】:

        • 这里的小错误。如果key 已存在于cacheMap 中,则add 可能会在将节点添加到lruList 后引发异常。要解决此问题,要么颠倒方法调用的顺序,因此首先调用 cacheMap.Add,或者添加代码以检查密钥是否已存在并以不同方式处理(即作为更改处理并调整 lruList)。跨度>
        • 如果我错了请纠正我,但我们不能完全消除 LRUCacheItem 类,只保留一个 LinkedList 和 Dictionary 吗?
        • @mclaassen LRUCacheItem 在这里需要,因为您想使用 O(1) 的 LinkedList.Remove(LinkedListNode) 而不是 O(n) 的 LinkedList.Remove(Value),这使得非常大的性能差异。因此,使用 Dictionary) 您可以按键获取 LinkedListNode ,然后将其从 O(1) 中的 LinkedList 中删除
        • 刚刚注意到 .NET Core 上没有 MethodImplOptions.Synchronized 但可以使用 lock 语句实现相同的功能。
        • add() 中有一个错误,如果 key 已经存在,lruList 仍然会附加一个新节点,而旧节点会留在列表中。解决方法是检查一个节点是否已经存在(cacheMap.TryGetValue()),如果存在则删除它。
        【解决方案6】:

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

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

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

        【讨论】:

          【解决方案7】:

          发现你在谷歌搜索时回答,也发现了这个:

          http://code.google.com/p/csharp-lru-cache/

          csharp-lru-cache:LRU缓存集合类库

          这是一个集合类 作为最近最少使用的函数 缓存。它实现了ICollection&lt;T&gt;, 但也暴露了其他三个成员:

          • Capacity,最大项数 缓存可以包含。一旦 集合已满,添加一个 缓存中的新项目将导致 最近最少使用的项目是 丢弃。如果容量设置为 0 在构建时,缓存不会 自动丢弃物品。
          • Oldest, 最旧的(即最近最少使用的) 集合中的项目。
          • DiscardingOldestItem,引发了一个事件 当缓存即将丢弃它的 最旧的项目。这是一个极 简单的实现。虽然它的添加 和 Remove 方法是线程安全的,它 不应该用在沉重的 多线程环境,因为 整个集合在期间被锁定 那些方法。

          【讨论】:

            【解决方案8】:

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

            基本功能:

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

            源代码:http://csharptest.net/browse/src/Library/Collections/LurchTable.cs

            GitHub:https://github.com/csharptest/CSharpTest.Net.Collections

            HTML 帮助:http://help.csharptest.net/

            PM> 安装包CSharpTest.Net.Collections

            【讨论】:

            • ➕ 1 new LurchTable&lt;string, Foo&gt;(LurchTableOrder.Access, 10*1000) 准备好了。
            【解决方案9】:

            这会将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; }
                    }
                }
            }
            

            【讨论】:

            • TryGetValueAddGet 之间存在一些代码重复。不能通过调用另外两个来实现Get吗?
            • 如果您根据第一个 TryGetValue() 实现 Get(),如果失败,则执行 Add(),当另一个线程潜入 Add() 之间时,您会创建一个潜在错误你的两个电话。话虽如此,您当然可以将公共代码提取到私有方法中,以便在正确持有锁后调用。
            【解决方案10】:

            我实现了一个专为并发工作负载设计的线程安全伪 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
            

            【讨论】:

            • 这是正确答案。
            【解决方案11】:

            【讨论】:

              猜你喜欢
              • 2012-10-23
              • 2010-09-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-10-30
              • 1970-01-01
              相关资源
              最近更新 更多