【问题标题】:LFU Cache in C#?C#中的LFU缓存?
【发布时间】:2010-10-31 11:49:49
【问题描述】:

在 C# 中是否有现成的 LFU 缓存可用?

【问题讨论】:

  • 我想看看你最终编写/使用的代码。
  • 写了它然后删除它,因为有更好的方法(而且我的代码效率非常低)。
  • 标记为正确的答案,并没有真正回答问题。 LFU 比 LRU 更难实现
  • @Sam:是的,确实如此。 ajmastream 说它不存在。 AFAIK,这是正确的答案。 :)
  • 它现在确实存在,请参阅我的回答。还可以改进。

标签: c# caching


【解决方案1】:

我相信还有其他人。

这是一个基本的 LFU 实现,用于 C# 的老化,我刚刚敲了,它并不完美,但它是一个很好的起点: 注意:这个实现不是线程安全的。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LFU {

    class LFUCache<TKey,TValue> {

        Dictionary<TKey, LinkedListNode<CacheNode>> cache = new Dictionary<TKey, LinkedListNode<CacheNode>>();
        LinkedList<CacheNode> lfuList = new LinkedList<CacheNode>();

        class CacheNode {
            public TKey Key { get; set; }
            public TValue Data { get; set; }
            public int UseCount { get; set; }
        } 

        int size;
        int age = 0;
        int agePolicy;

        public LFUCache(int size) : this(size, 1000) {
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="size"></param>
        /// <param name="agePolicy">after this number of gets the cache will take 1 off all UseCounts, forcing old stuff to expire.</param>
        public LFUCache(int size, int agePolicy) {
            this.agePolicy = 1000;
            this.size = size;
        }

        public void Add(TKey key, TValue val) {
            TValue existing;
            if (!TryGet(key, out existing)) {
                var node = new CacheNode() {Key = key, Data = val, UseCount = 1};
                if (lfuList.Count == size) {
                    cache.Remove(lfuList.First.Value.Key);
                    lfuList.RemoveFirst();
                }

                var insertionPoint = Nodes.LastOrDefault(n => n.Value.UseCount < 2);

                LinkedListNode<CacheNode> inserted; 

                if (insertionPoint == null) {
                    inserted = lfuList.AddFirst(node);
                } else {
                    inserted = lfuList.AddAfter(insertionPoint, node);
                }
                cache[key] = inserted;
            }
        }

        IEnumerable<LinkedListNode<CacheNode>> Nodes {
            get {
                var node = lfuList.First;
                while (node != null) {
                    yield return node;
                    node = node.Next;
                }
            }
        }

        IEnumerable<LinkedListNode<CacheNode>> IterateFrom(LinkedListNode<CacheNode> node) {
            while (node != null) {
                yield return node;
                node = node.Next;
            }
        }

        public TValue GetOrDefault(TKey key) {
            TValue val;
            TryGet(key, out val);
            return val;
        }

        public bool TryGet(TKey key, out TValue val) {

            age++;
            if (age > agePolicy) {
                age = 0;
                foreach (var node in cache.Values) {
                    var v = node.Value;
                    v.UseCount--;
                }
            }

            LinkedListNode<CacheNode> data;
            bool success = false;

            if (cache.TryGetValue(key, out data)) {
                var cacheNode = data.Value;
                val = cacheNode.Data;
                cacheNode.UseCount++;

                var insertionPoint = IterateFrom(data).Last(n => n.Value.UseCount <=  cacheNode.UseCount);

                if (insertionPoint != data) {
                    lfuList.Remove(data);
                    lfuList.AddAfter(insertionPoint, data);
                }

            } else {
                val = default(TValue);
            }

            return success;
        }
    }

    class Program {


        public static void Assert(bool condition, string message) {
            if (!condition) {
                Console.WriteLine("Assert failed : " + message);
                throw new ApplicationException("Test Failed"); 
            }
        }

        public static void RunAllTests(Program prog){
            foreach (var method in prog.GetType().GetMethods()) {
                if (method.Name.StartsWith("Test")) {
                    try {
                        method.Invoke(prog, null);
                        Console.WriteLine("Test Passed: " + method.Name);
                    } catch (Exception) {
                        Console.WriteLine("Test Failed : " + method.Name);
                    }

                }
            }
        }

        public void TestItemShouldBeThereOnceInserted() {
            var cache = new LFUCache<string, int>(3);

            cache.Add("a", 1);
            cache.Add("b", 2);
            cache.Add("c", 3);
            cache.Add("d", 4); 

            Assert(cache.GetOrDefault("a") == 0, "should get 0");
            Assert(cache.GetOrDefault("b") == 2, "should get 2");
            Assert(cache.GetOrDefault("c") == 3, "should get 3");
            Assert(cache.GetOrDefault("d") == 4, "should get 4");
        }

        public void TestLFUShouldWorkAsExpected() {

            var cache = new LFUCache<string, int>(3);

            cache.Add("a", 1);
            cache.Add("b", 2);
            cache.Add("c", 3);

            cache.GetOrDefault("a");
            cache.GetOrDefault("a");
            cache.GetOrDefault("b");

            cache.Add("d", 4);
            cache.Add("e", 5);


            Assert(cache.GetOrDefault("a") == 1, "should get 1");
            Assert(cache.GetOrDefault("b") == 2, "should get 0");
            Assert(cache.GetOrDefault("c") == 0, "should get 0");
            Assert(cache.GetOrDefault("d") == 0, "should get 4");
            Assert(cache.GetOrDefault("e") == 5, "should get 5");

        } 


        static void Main(string[] args) {
            RunAllTests(new Program());
            Console.ReadKey();
        }


    }
}

【讨论】:

    【解决方案2】:

    从 .NET 3.5 开始,框架中没有这样的集合Ayende 创建了一个 Least Recently Used 集。这可能是一个好的开始 (code)。

    【讨论】:

    • 我添加了一个实现,所以这不再是正确的答案:p
    • Sam,从仅框架的角度来看,我的回答仍然是正确的。你的回答非常好,我什至会投票,但我是第一个 B-)
    • 我更新了代码链接以转到github上的最新repo
    【解决方案3】:

    您可以查看MS "Velocity"。我不确定他们提供了哪些野蛮策略,但可能值得一看。此外,您还可以扩展 Caching Application Block 提供的策略。

    【讨论】:

      【解决方案4】:

      我制作了我的,FWIW:

      https://github.com/ysharplanguage/GenericMemoryCache

      (它不仅支持 LFU,还支持 LRU/MRU,如果愿意,还可以扩展到其他驱逐和替换策略)

      '希望这会有所帮助,

      【讨论】:

        【解决方案5】:

        试试CacheCrow,它是一个简单的 LFU,基于时间的缓存。

        【讨论】:

          猜你喜欢
          • 2019-04-01
          • 2023-04-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-08-12
          • 2019-01-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多