TL;DR 我正在寻找一种方法,可以从IComparer<T>
中获取IEqualityComparer<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统计数据,最好只能得到更低的缓存命中率。另一种情况也不如预期。此外,如果键更复杂(类似Tuple<string,DateTime,DateTime>
),那么可能会更严重地搞砸它。这就是为什么我希望在构造函数中只有一个比较器参数,但这似乎行不通。我可以借助
IComparer<T>.Compare()
创建IEqualityComparer<T>.Equals()
,但我陷入了IEqualityComparer<T>.GetHashCode()
的困境——这非常重要,正如您所知道的那样。如果我能够访问比较器的私有属性以检查它是否区分大小写,我将使用CompareInfo
来获取哈希码。我喜欢这种使用2个不同数据结构的方法,因为它给我可接受的性能和可控的内存消耗——在我的笔记本电脑上,缓存大小为10,000个元素时,每秒大约可以添加约500,000个缓存。
Dictionary<TKey,TValue>
仅用于以O(1)找到数据,而SortedSet<CacheItem>
则以O(log n)插入数据,通过调用lfuList.Min
在O(log n)中查找要删除的元素,并在O(log n)中找到要增加使用计数的条目。欢迎任何解决此问题的建议。我会赞赏任何想法,包括不同的设计。
IEqualityComparer<T>
和IComparer<T>
的单个比较器参数。这样,至少您不必将同一对象传递给两个不同的参数。 - Mike ZboraySortedSet
。如果你增加了UseCount
,那么这个集合中只会向上改变一个位置。也许有O(1)的触摸实现。插入应该是O(1),因为它被添加到列表的末尾(也许SortedSet
会这样做)。我将尝试一个新的示例实现。 - Sebastian Schumann