TL; DR我正在寻找一种从IEqualityComparer<T>获取IComparer<T>的方法,无论哪种数据类型是T,如果Tstring,都包括不区分大小写的选项。或者我需要针对此问题的其他解决方案。

这是完整的故事:我正在使用LFU策略实现简单的通用缓存。要求是必须能够选择缓存是区分大小写还是不区分大小写-如果string恰好是缓存键的数据类型(这不是必需的)。在该解决方案中,我主要为之开发缓存,我期望有数千亿个缓存查找,最大缓存大小为100.000个条目。因此,我立即放弃使用任何导致分配的字符串操作(例如.ToLower().GetHashCode()等),而选择使用IComparerIEqualityComparer,因为它们是标准的BCL功能。此缓存的用户可以将比较器传递给构造函数。以下是相关代码片段:

public class LFUCache<TKey,TValue>
{
    private readonly Dictionary<TKey,CacheItem> entries;
    private readonly SortedSet<CacheItem> lfuList;

    private class CacheItem
    {
        public TKey Key;
        public TValue Value;
        public int UseCount;
    }

    private class CacheItemComparer : IComparer<CacheItem>
    {
        private readonly IComparer<TKey> cacheKeyComparer;

        public CacheItemComparer(IComparer<TKey> cacheKeyComparer)
        {
            this.cacheKeyComparer = cacheKeyComparer;
            if (cacheKeyComparer == null)
                this.cacheKeyComparer = Comparer<TKey>.Default;
        }

        public int Compare(CacheItem x, CacheItem y)
        {
            int UseCount = x.UseCount - y.UseCount;
            if (UseCount != 0) return UseCount;
            return cacheKeyComparer.Compare(x.Key, y.Key);
        }
    }

    public LFUCache(int capacity, IEqualityComparer<TKey> keyEqualityComparer,
                  IComparer<TKey> keyComparer)  // <- here's my problem
    {
        // ...
        entries = new Dictionary<TKey, CacheItem>(keyEqualityComparer);
        lfuList = new SortedSet<CacheItem>(new CacheItemComparer(keyComparer));
    }
    // ...
}
keyEqualityComparer用于管理缓存条目(例如,如果用户愿意,键“ABC”和“abc”相等)。 keyComparer用于管理按UseCount排序的缓存条目,以便轻松选择最不常用的缓存条目(在CacheItemComparer类中实现)。

使用自定义比较的示例正确用法:
var cache = new LFUCache<string, int>(10000,
    StringComparer.InvariantCultureIgnoreCase,
    StringComparer.InvariantCultureIgnoreCase);

(这看起来很愚蠢,但是StringComparer同时实现了IComparer<string>IEqualityComparer<string>。)问题是,如果用户提供不兼容的比较器(即,不区分大小写的keyEqualityComparer和区分大小写的keyComparer),则最有可能的结果是无效的LFU统计信息,从而降低了缓存命中率最好的。另一种情况也比期望的要少。同样,如果 key 更复杂(我将得到类似于Tuple<string,DateTime,DateTime>的东西),则可能会更严重地将其弄乱。

这就是为什么我只想在构造函数中有一个比较器参数,但这似乎行不通。我可以在IEqualityComparer<T>.Equals()的帮助下创建IComparer<T>.Compare(),但是我被IEqualityComparer<T>.GetHashCode()所困-正如您所知道的,这非常重要。如果我可以访问比较器的私有(private)属性以检查它是否区分大小写,则可以使用CompareInfo来获取哈希码。

我喜欢这种具有2种不同数据结构的方法,因为它为我提供了可接受的性能和可控的内存消耗-在我的笔记本电脑上,每秒大约有500.000个高速缓存添加,高速缓存大小为10.000个元素。 Dictionary<TKey,TValue>仅用于在O(1)中查找数据,并且SortedSet<CacheItem>在O(log n)中插入数据,通过在O(log n)中调用lfuList.Min查找要删除的元素,并在O(1)中也找到增加使用计数的条目登录n)。

欢迎提供任何有关解决此问题的建议。我将不胜感激任何想法,包括不同的设计。

最佳答案

正如我在评论中提到的那样,您可以添加一个辅助方法,该方法可以使基本用例的工作变得简单一些:

public class LFUCache<TKey,TValue>
{
    public static LFUCache<TKey, TValue> Create<TComp>(int capacity, TComp comparer) where TComp : IEqualityComparer<TKey>, IComparer<TKey>
    {
        return new LFUCache<TKey, TValue>(capacity, comparer, comparer);
    }
}

并且您将像这样使用它:
var cache = LFUCache<string, int>.Create(10000, StringComparer.InvariantCultureIgnoreCase);

关于c# - 有没有办法从IComparer派生IEqualityComparer?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/30851055/

10-11 00:47