C# 字典内存管理

10

我有一个可能包含1000万个以上唯一键的Dictionary<string,int>。我想尽量减少内存使用,同时仍然保持字典的功能。

我的想法是将字符串的哈希值作为长整型存储,这样可以将应用程序的内存使用减少到可接受的水平(从约1.5 GB降至约0.5 GB),但我对自己的方法感到不太满意。

long longKey=
BitConverter.ToInt64(cryptoTransformSHA1.ComputeHash(enc.GetBytes(strKey)), 0);

基本上,这个方法是截取SHA1哈希的结尾,并将其第一块放入long中,然后将其用作键。虽然这个方法可以工作,至少对于我测试的数据而言,但由于键冲突的可能性增加,我认为这不是一个非常可靠的解决方案。
是否有其他减少字典内存占用的方法,或者我上面的方法并没有我想象中那么糟糕?
[编辑] 澄清一下,我需要保持使用字符串查找字典中的值的能力。在字典中存储实际字符串会占用太多内存。相反,我想使用一个Dictionary<long,int>,其中long是字符串的哈希函数的结果。

1
我怀疑使用64位哈希算法发生碰撞的可能性是现实的。 - Jon B
我想也是这样,但只是将字节一分为二似乎有点靠不住。 - Nicholas Mancuso
我开始意识到这个问题可能会是一个真正的麻烦。请保持我们的信息更新,我会非常关注你的最终解决方案。 - Konrad Rudolph
首先,这些字符串有多大? - Greg Dean
6个回答

12

我最近做了类似的事情,由于我的应用程序有一些独特的原因,我没有使用数据库。事实上,我试图停止使用数据库。我发现,在3.5版本中,GetHashCode显著改进了。一个重要的注意点是,永远不要持久地存储从GetHashCode得到的结果。千万不要这样做。它们不能保证在框架版本之间保持一致。

因此,您确实需要对数据进行分析,因为不同的哈希函数可能在您的数据上表现更好或更差。您还需要考虑速度。作为一般规则,即使哈希值数量达到数十亿,加密哈希函数也不应该有太多冲突。对于我需要唯一性的东西,我通常使用SHA1 Managed。总的来说,CryptoAPI的性能非常糟糕,即使底层哈希函数表现良好。

对于64位哈希,我目前使用Lookup3和FNV1,它们都是32位哈希,结合在一起使用。为了发生冲突,两者都需要发生冲突,这在数亿个哈希中是数学上不可能的,而我也没有看到在大约1亿个哈希中出现过。您可以在网络上公开找到这两个哈希函数的代码。

仍然要进行自己的分析。对我有效的方法可能对您无效。实际上,在我的办公室里,不同的应用程序具有不同的要求,使用不同的哈希函数或哈希函数组合。

我建议避免使用未经证明的哈希函数。与认为自己应该编写它们的人一样多的哈希函数。请做好研究和测试测试测试。


我实现了您的64位哈希想法的一个版本,初步测试效果不错。我将进行进一步的测试,但这似乎是在内存大小和访问时间之间取得最佳平衡的解决方案,非常适合我的目的。 - blogsdon
很酷。我喜欢64位哈希技术。你用了哪些哈希函数? - Steve Severance
+1 只回答问题,不推荐关系型数据库。 - lubos hasko
谢谢。关系型数据库很好,但有时候你不想使用它。我把这个决定留给人们自己判断。 - Steve Severance

7

你是否考虑使用带有非聚集索引的数据库来处理1000万条记录?对于这种情况,数据库有更多的技巧。

根据定义和任何算法,哈希都有可能发生碰撞,特别是在高负载情况下。根据场景,我会非常谨慎地使用它。

使用字符串可能需要占用空间,但是它是可靠的……如果你使用x64,那么这不需要太大的空间(尽管它肯定算得上“大”;-p)。


5
顺便提一下,密码哈希/哈希函数在字典中效果非常糟糕。它们很大且速度慢。解决了一个问题(大小)之后,你只会引入另一个更严重的问题:函数将不再均匀地分配输入,从而破坏了逼近无碰撞寻址的好哈希的单个最重要属性(正如你自己似乎已经注意到的那样)。
编辑:正如安德鲁所指出的那样,GetHashCode是这个问题的解决方案,因为这就是它的预期用途。并且像在真正的词典中一样,您将不得不解决冲突。其中最好的方案之一是双重散列。不幸的是,唯一100%可靠的方法将是实际存储原始值。否则,您将创建无限压缩,我们知道这是不可能存在的。

实际上,这就是他正在做的事情。与其使用Dict<string, int>,他使用了Dict<long, int>,并且密钥是原始字符串的加密哈希值,而以前string.gethashcode在原始样本中导致重复的密钥。 - Nicholas Mancuso
尼古拉斯,你是对的 - 但是(残缺不全的)加密哈希仍然是一个糟糕的哈希,即使在双重哈希中使用。 - Konrad Rudolph
你可以通过将签名封装在一个类中,并假装签名本身是某个不透明的对象来改变那个沮丧的表情。我下面的示例就是这样做的。请记住,他应该无论如何都要使用数据库... - user7116

3
为什么不直接使用GetHashCode()来获取字符串的哈希值?

我不知道GetHashCode不可靠 - 有更多信息吗? - Andrew Hare
Diadistis: 你为什么这么说?这就是函数的目的!没有其他通用函数能够表现得一样好。 - Konrad Rudolph
据我理解,String类型的GetHashCode是可靠(确定性的),但不是唯一的。 - Brian Genisio
http://blogs.msdn.com/brada/archive/2003/09/30/50396.aspx http://vkreynin.wordpress.com/2008/07/05/explaining-gethashcode-method/ - Diadistis

3

只需获取SQLite。您不太可能超越它,即使您这样做,也可能不值得花费时间/精力/复杂性。

SQLite。


2

在我以前使用的哈希表实现中,哈希会带您进入存储有相同哈希值的其他对象的链接列表的桶中。哈希值不是唯一的,但足以将数据分成易于管理的列表(有时只有2或3个元素),然后您可以搜索这些列表以找到实际的项。

一个好的哈希的关键不在于它的唯一性,而在于它的速度和分布能力......您希望它尽可能均匀地分布。


字典不是这样工作的。它不允许键冲突。你需要使用不同的数据结构来处理冲突,同时需要存储哈希键和真实键 -- 除非你也知道你要查找的值。这不会节省任何内存。 - tvanfosson
哈希键可能是同余的,但不是等价的。他正在使用一个哈希字符串作为键。这就是为什么他不能使用string.GetHashCode()作为键,因为在样本大小给定的情况下会出现重复。 - Nicholas Mancuso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接