C# 数据结构选择,用于高速高效地检测字符串的重复

C# 数据结构选择,用于高速高效地检测字符串的重复,c#,hash,deduplication,C#,Hash,Deduplication,我有一个有趣的问题,可以通过多种方式解决: 我有一个接受字符串的函数 如果此函数以前从未见过此字符串,则需要执行一些处理 如果函数以前见过字符串,则需要跳过处理 在指定的时间之后,函数应该接受重复的字符串 此函数每秒可以调用数千次,并且字符串数据可能非常大 这是对实际应用程序的高度抽象的解释,只是为了问题的目的,试图深入到核心概念 该函数需要存储状态以检测重复项。它还需要存储一个相关的时间戳,以便使副本过期 它不需要存储字符串,只要不存在由于冲突而导致的误报(使用完美哈希?),并且哈希函数的

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

  • 我有一个接受字符串的函数
  • 如果此函数以前从未见过此字符串,则需要执行一些处理
  • 如果函数以前见过字符串,则需要跳过处理
  • 在指定的时间之后,函数应该接受重复的字符串
  • 此函数每秒可以调用数千次,并且字符串数据可能非常大
这是对实际应用程序的高度抽象的解释,只是为了问题的目的,试图深入到核心概念

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

它不需要存储字符串,只要不存在由于冲突而导致的误报(使用完美哈希?),并且哈希函数的性能足够好,字符串的唯一哈希就可以了

简单的实现(用C#)是:

字典
虽然为了降低内存占用和潜在地提高性能,我正在评估一个定制的数据结构来处理这个问题,而不是一个基本的哈希表

那么,考虑到这些限制,您会使用什么

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

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

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

我认为字典是开箱即用的数据结构所能提供的最好的。由于可以存储定义了自定义比较的对象,所以可以轻松避免将字符串保留在Memory中,而只需保存可以获取整个字符串的位置。即具有以下值的对象:

stringLocation.fileName="file13.txt";
stringLocation.fromOffset=100;
stringLocation.toOffset=345;
expiration= "2012-09-09T1100";
hashCode = 123456;
其中,cutomom comparer将根据需要从文件返回保存的哈希代码或检索字符串,并执行比较

只要不存在false,字符串的唯一散列就可以了 由于碰撞而产生的正值

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

使用散列码意味着存在误报,只是它们很少,不会造成性能问题


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

如果内存占用是可以容忍的,我建议字符串使用
Hashset
,并使用队列存储
元组。比如:

Hashset<string> Strings = new HashSet<string>();
Queue<Tuple<DateTime, String>> Expirations = new Queue<Tuple<DateTime, String>>();
Hashset Strings=newhashset();
队列过期=新队列();
现在,当输入字符串时:

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));
}
if(Strings.Add)
{
//字符串是新的。请处理它。
//并将其添加到过期队列中
Enqueue(新元组(DateTime.Now+ExpireTime,s));
}
而且,你必须在某个地方检查过期时间。也许每次获得新字符串时,您都会执行以下操作:

while (Expirations.Count > 0 && Expirations.Peek().Item1 < DateTime.Now)
{
    var e = Expirations.Dequeue();
    Strings.Remove(e.Item2);
}
while(Expirations.Count>0&&Expirations.Peek().Item1
在这里很难击败Hashset的性能。当然,您存储的是字符串,但这将是保证没有误报的唯一方法

您也可以考虑使用除了DATETIME以外的时间戳。现在。我通常做的是在程序启动时启动一个秒表,然后使用

elapsedmillesons
值。这样可以避免夏令时更改、系统自动更新时钟(使用NTP)或用户更改日期/时间时出现的潜在问题

上述解决方案是否适用于您将取决于您是否能够承受存储字符串的内存冲击

在发布“其他信息”后添加:

如果这将由多个线程访问,我建议使用
ConcurrentDictionary
而不是
Hashset
,使用
BlockingCollection
而不是
Queue
。或者,您可以使用
lock
同步对非并发数据结构的访问


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

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

1) 仅存储字符串的散列,这意味着可能发生散列冲突(当散列比字符串短时)。好的散列函数(MD5、SHA1等)使得这种冲突几乎不可能发生,所以这只取决于它是否足够快

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


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

字典是否已被证明无法通过您所需的性能指标?目前,这是一个概念性问题
while (Expirations.Count > 0 && Expirations.Peek().Item1 < DateTime.Now)
{
    var e = Expirations.Dequeue();
    Strings.Remove(e.Item2);
}