数据结构选择,用于高速和内存有效地检测字符串的重复

时间:2012-04-14 04:51:30

标签: c# hash deduplication

我有一个有趣的问题可以通过多种方式解决:

  • 我有一个接受字符串的函数。
  • 如果此函数以前从未见过这个字符串,则需要执行一些处理。
  • 如果函数之前看过字符串,则需要跳过处理。
  • 在指定的时间后,该函数应接受重复的字符串。
  • 此函数可能每秒被调用数千次,字符串数据可能非常大。

这是对实际应用程序的一个高度抽象的解释,只是为了问题的目的而试图深入到核心概念。

该函数需要存储状态才能检测重复项。它还需要存储一个相关的时间戳,以使重复过期。

它不需要存储字符串,字符串的唯一哈希就可以了,假设没有因冲突导致的误报(使用完美的哈希?),并且哈希函数的性能足够。

天真的实现很简单(在C#中):

 Dictionary<String,DateTime>

虽然为了降低内存占用并可能提高性能,我正在评估一个自定义数据结构来处理这个而不是基本哈希表。

所以,鉴于这些限制,你会用什么?

编辑,一些可能会改变建议实施的其他信息:

  • 99%的字符串不会重复。
  • 几乎所有副本都会背靠背或几乎顺序到达。
  • 在现实世界中,将从多个工作线程调用该函数,因此需要同步状态管理。

4 个答案:

答案 0 :(得分:5)

我不相信可以在不知道完整的值集的情况下构造“perfect hash”(特别是在C#int且值的数量有限的情况下)。因此,任何类型的散列都需要能够比较原始值。

我认为字典是开箱即用数据结构的最佳选择。由于您可以存储定义了自定义比较的对象,因此您可以轻松避免将字符串保存在记忆中,只需保存可以获取整个字符串的位置。即具有以下值的对象:

stringLocation.fileName="file13.txt";
stringLocation.fromOffset=100;
stringLocation.toOffset=345;
expiration= "2012-09-09T1100";
hashCode = 123456;

如果需要,cutomom比较器将从文件返回保存的hashCode或retrive字符串并执行比较。

答案 1 :(得分:2)

  

字符串的唯一哈希值很好,前提是没有错误   因碰撞而产生的积极因素

如果您希望哈希码比字符串短,那是不可能的。

使用哈希码意味着存在误报,只是它们很少见,不会出现性能问题。

我甚至会考虑仅从字符串的一部分创建哈希码,以使其更快。即使这意味着你会得到更多的误报,也可能会提高整体表现。

答案 2 :(得分:2)

如果内存占用空间可以容忍,我建议使用Hashset<string>表示字符串,并建议存储Tuple<DateTime, String>的队列。类似的东西:

Hashset<string> Strings = new HashSet<string>();
Queue<Tuple<DateTime, String>> Expirations = new Queue<Tuple<DateTime, String>>();

现在,当一个字符串进来时:

if (Strings.Add(s))
{
    // string is new. process it.
    // and add it to the expiration queue
    Expirations.Enqueue(new Tuple<DateTime, String>(DateTime.Now + ExpireTime, s));
}

而且,在某个地方你必须检查过期。也许每次你得到一个新的字符串,你都会这样做:

while (Expirations.Count > 0 && Expirations.Peek().Item1 < DateTime.Now)
{
    var e = Expirations.Dequeue();
    Strings.Remove(e.Item2);
}

这里很难超越Hashset的表现。当然,你要存储字符串,但这将是保证没有误报的唯一方法。

您也可以考虑使用DateTime.Now以外的时间戳。我通常做的是在程序启动时启动Stopwatch,然后使用ElapsedMilliseconds值。这可避免在夏令时更改期间发生的潜在问题,系统自动更新时钟(使用NTP)或用户更改日期/时间时。

上述解决方案是否适合您将取决于您是否能够忍受存储字符串的内存损失。

在发布“附加信息”后添加:

如果多线程会访问此项,我建议您使用ConcurrentDictionary而不是HashsetBlockingCollection而不是Queue。或者,您可以使用lock来同步对非并发数据结构的访问。

如果确实99%的字符串不会重复,那么您几乎肯定需要一个可以从字典中删除内容的过期队列。

答案 3 :(得分:1)

如果存储整个字符串的内存占用不可接受,则只有两种选择:

1)只存储字符串的散列,这意味着散列冲突的可能性(当散列比字符串短时)。良好的散列函数(MD5,SHA1等)使得这种碰撞几乎不可能发生,因此它只取决于它是否足够快以达到您的目的。

2)使用某种无损压缩。字符串通常具有良好的压缩比(约10%),而某些算法(如ZIP)可让您在快速(和低效率)和慢速(高压缩比)压缩之间进行选择。压缩字符串的另一种方法是将它们转换为UTF8,这样做快速且容易,并且非unicode字符串的压缩率接近50%。

无论您选择何种方式,它总是在内存占用和散列/压缩速度之间进行权衡。您可能需要进行一些基准测试以选择最佳解决方案。