TL; DR我正在寻找一种从IEqualityComparer<T>
获取IComparer<T>
的方法,无论哪种数据类型是T
,如果T
是string
,都包括不区分大小写的选项。或者我需要针对此问题的其他解决方案。
这是完整的故事:我正在使用LFU策略实现简单的通用缓存。要求是必须能够选择缓存是区分大小写还是不区分大小写-如果string
恰好是缓存键的数据类型(这不是必需的)。在该解决方案中,我主要为之开发缓存,我期望有数千亿个缓存查找,最大缓存大小为100.000个条目。因此,我立即放弃使用任何导致分配的字符串操作(例如.ToLower().GetHashCode()
等),而选择使用IComparer
和IEqualityComparer
,因为它们是标准的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/