【问题标题】:C# Dictionary Memory ManagementC# 字典内存管理
【发布时间】:2010-09-27 14:36:40
【问题描述】:

我有一个Dictionary<string,int>,它有可能包含超过 10+ 百万个唯一键。我正在尝试减少所需的内存量,同时仍保持字典的功能。

我的想法是将字符串的哈希存储为 long,这会将应用程序的内存使用量减少到可接受的量(~1.5 gig 到 ~.5 gig),但我对我的感觉不太好这样做的方法。

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

基本上,这会切断 SHA1 哈希的末尾,并将其第一块放入一个 long 中,然后我将其用作密钥。虽然这可行,但至少对于我正在测试的数据而言,我觉得这不是一个非常可靠的解决方案,因为键冲突的可能性增加了。

有没有其他方法可以减少字典的内存占用,或者我上面的方法没有我想象的那么可怕?

[编辑] 为了澄清,我需要保持使用字符串查找字典中包含的值的能力。将实际字符串存储在字典中会占用大量内存。我想做的是使用Dictionary<long,int>,其中 long 是字符串上散列函数的结果。

【问题讨论】:

  • 我怀疑 64 位哈希是否存在冲突的可能性。
  • 我想也是这样,但只是将字节“切”成两半似乎有点不确定。
  • 我开始发现这个问题可能真的很糟糕。请随时通知我们,我会对您的最终解决方案非常感兴趣。
  • 首先字符串有多大?

标签: c# data-structures memory-management dictionary


【解决方案1】:

所以我最近做了一些类似的事情,出于某些对我的应用程序来说相当独特的原因,我没有使用数据库。事实上,我试图停止使用数据库。我发现 GetHashCode 在 3.5 中得到了显着改进。一个重要的注意事项,永远不要持久存储来自 GetHashCode 的结果。永远不能。不保证它们在框架版本之间保持一致。

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

对于 64 位散列,我目前一起使用 Lookup3 和 FNV1,它们都是 32 位散列。要发生碰撞,两者都需要发生碰撞,这在数学上是不可能的,而且我还没有看到超过 1 亿个哈希值发生过。您可以在网络上找到公开可用的代码。

仍然进行您自己的分析。对我有用的东西可能对你不起作用。实际上,在我的办公室内部,具有不同要求的不同应用程序实际上使用了不同的哈希函数或哈希函数的组合。

我会避免任何未经证实的哈希函数。散列函数的数量与认为应该编写它们的人一样多。做你的研究和测试测试。

【讨论】:

  • 我实现了你的 64 位哈希想法的一个版本,初步测试很顺利。我将进行一些进一步的测试,但这看起来是我的目的在内存大小和访问时间之间进行最佳权衡的解决方案。
  • 酷。我喜欢 64 位散列技术。您使用了哪些哈希函数?
  • +1 用于实际回答问题而不是尝试推荐关系数据库。
  • 谢谢。关系数据库很棒,但有时您不想使用它。在那个时候,我把它留给人们判断。
【解决方案2】:

拥有 1000 万条记录,您是否考虑过使用具有非聚集索引的数据库?对于这类事情,数据库还有很多技巧。

根据定义,在任何算法下,散列都有可能发生冲突——尤其是在大容量的情况下。根据情况,我会对此非常谨慎。

使用字符串可能会占用空间,但它是可靠的...如果您使用的是 x64,则不需要太大(尽管它绝对算作“大”;-p)

【讨论】:

    【解决方案3】:

    顺便说一句,加密散列/散列函数对字典非常不利。它们又大又慢。通过解决一个问题(大小),您只引入了另一个更严重的问题:该函数将不再均匀分布输入,从而破坏了用于接近无冲突寻址的良好哈希的一个最重要的属性(如你似乎已经注意到了自己)。

    /EDIT:正如 Andrew 所指出的,GetHashCode 是解决此问题的 解决方案,因为这是它的预期用途。就像在真正的字典中一样,您将不得不解决碰撞问题。最好的方案之一是double hashing。不幸的是,唯一 100% 可靠的方法是实际存储原始值。否则,你会创建一个无限压缩,我们知道这是不存在的。

    【讨论】:

    • 实际上就是他正在做的事情。而不是 Dict 它的 Dict 和密钥是原始字符串的加密哈希,而在 string.gethashcode 之前导致原始样本中的重复密钥。
    • Nicholas,你是对的——但是(残缺的)cryto 散列仍然是一个糟糕的散列,即使在双重散列中使用也是如此。
    • 您可以通过将签名封装在一个类中并假装签名本身是一些不透明的对象来扭转这种皱眉。我下面的例子就是这样做的。请记住,他无论如何都应该使用数据库...
    【解决方案4】:

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

    【讨论】:

    【解决方案5】:

    通过我过去使用过的哈希表实现,哈希会将您带到一个存储桶,该存储桶通常是具有相同哈希的其他对象的链接列表。哈希不是唯一的,但它们足以将您的数据拆分为非常易于管理的列表(有时只有 2 或 3 长),然后您可以搜索这些列表以找到您的实际项目。

    一个好的散列的关键不是它的唯一性,而是它的速度和分布能力……你希望它尽可能均匀地分布。

    【讨论】:

    • 字典不能这样工作。它不会允许键冲突。您必须使用不同的数据结构并处理冲突,您需要存储散列键和真实键——除非您也知道要查找的值。这不会节省任何内存。
    • 散列键可能是一致的,但不是等价的。他使用散列字符串作为密钥。这就是为什么不能使用 string.GetHashCode() 作为键的原因,因为给定样本大小的欺骗。
    【解决方案6】:

    去获取 SQLite。您不太可能击败它,即使您做到了,也可能不值得花时间/精力/复杂性。

    SQLite。

    【讨论】:

      猜你喜欢
      • 2014-06-04
      • 1970-01-01
      • 2010-10-11
      • 2014-03-30
      • 2010-09-06
      • 2018-12-17
      • 1970-01-01
      相关资源
      最近更新 更多