我应该使用哪种数据结构来存储哈希值?

时间:2009-12-24 08:32:10

标签: optimization data-structures diskspace

我有一个哈希表,我想存储到磁盘。该列表如下所示:

<16-byte key                   > <1-byte result>
a7b4903def8764941bac7485d97e4f76 04
b859de04f2f2ff76496879bda875aecf 03
etc...

有1-5百万个条目。目前我只是将它们存储在一个文件中,每个条目17个字节乘以条目数。那个文件是几十兆字节。我的目标是以一种优先考虑磁盘空间然后查找时间的方式存储它们。插入时间并不重要。

最好的方法是什么?我希望文件尽可能小。多个文件也可以。 Patricia trie? Radix trie?

无论我得到什么好的建议,我都会实施和测试。我会在这里发布结果供大家查看。

6 个答案:

答案 0 :(得分:4)

您可以按键对条目进行排序并进行二分查找。

固定大小的键和数据条目意味着您可以非常快速地从一行跳到另一行,并且只存储键和数据意味着您不会在元数据上浪费任何空间。

我认为你不会在磁盘空间上做得更好,查询时间是O(log(n))。插入时间很长,但你说没关系。

如果您真的愿意容忍长访问时间,请对表进行排序,然后将其分成一些大小的块并压缩它们。在开始时将每个块的偏移*和开始/结束键存储在文件的一部分中。使用此方案,您可以在线性时间内找到包含所需密钥的块,然后在解压缩块中执行二进制搜索。根据您愿意一次加载到内存中的文件大小,选择大小的块。

使用现成的压缩方案(如GZIP),您可以根据需要调整压缩比;较大的文件可能会有更快的查找时间。

我怀疑节省的空间会非常大,因为你的结构似乎主要是哈希。如果它们实际上是哈希值,则它们是随机的并且不会压缩得非常好。排序有助于提高压缩比,但不是一吨。

*使用标题查找要解压缩和使用的块的偏移量。

答案 1 :(得分:3)

500万条记录大约81MB - 可以在内存中使用数组。

正如您所描述的问题 - 它比哈希值更独特。 尝试使用哈希表来访问值(查看this link)。

如果存在我的误解并且这是真正的哈希 - 尝试在此之上构建第二个哈希级别。

哈希表也可以在磁盘上成功组织(例如,作为单独的文件)。

加成

具有良好搜索性能和较少开销的解决方案是:

  1. 定义散列函数,它从键生成整数值。
  2. 根据此函数生成的值对文件中的记录进行排序
  3. 存储每个哈希值开始的文件偏移量
  4. 找到价值:
    4.1。用函数计算它的哈希值 4.2。查找文件中的偏移量 4.3。从该位置开始读取文件中的记录,直到找不到下一个键的键或偏移量或文件结束。
  5. 还有一些必须指出的其他事项:

    • 哈希函数必须快速有效
    • 散列函数必须生成线性分布值或接近
    • 哈希值偏移表可以放在单独的文件中
    • 哈希值偏移表可以动态生成,在应用程序启动时顺序读取整个排序文件并存储在内存中
    • 在步骤4.3。记录必须以块为单位,而不是一个一个,才能生效。理想情况下,一次将所有带有计算哈希值的值读入内存。

    您可以找到一些哈希函数here的示例。

答案 2 :(得分:1)

简单的方法是否有效并将它们存储在sqlite database中?我不认为它会变小,但你应该获得非常好的查找​​性能,并且它很容易实现。

答案 3 :(得分:1)

首先 - 如果要优化磁盘空间,由于群集大小,多个文件不正常 - 当您创建大小为~100字节的文件时,每个群集大小的磁盘空间减少 - 例如2kB。

其次 - 在你的情况下,我将所有表存储在单个二进制文件中,按键中的字节值简单地按ASC排序。它将为您提供长度恰好等于entriesNumber * 17的文件,如果您不想使用归档,则该文件最小;其次,当您搜索密钥分割文件时,您可以使用time~log2(entriesNumber)进行非常快速的搜索分为两部分,并将边界上的键与所需的键进行比较。如果“border key”更大,则取第一部分文件,如果更大 - 则接第二部分。并再次分为两部分,等等。 因此,您将需要关于log2(entriesNumber)读取操作来搜索单个密钥。

答案 4 :(得分:1)

与文件设计一样,您越了解(并告诉我们)有关数据分布的信息越多越好。假设您的键值均匀分布在所有16字节键的集合中 - 如果您要存储哈希表,则应该为真 - 我建议将其他人已经建议的内容组合起来:

  • 这样的二进制数据属于二进制文件;不要让你的哈希值和值的简单表示形式为十六进制数字的字符串,这让你觉得这是字符串数据;

  • 文件大小可以将整个shebang保存在任何现代PC或服务器以及许多其他设备的内存中;

  • 键的前4个字节将可能的键组划分为16 ^ 4(= 65536)个子集;如果您的密钥是均匀分布的,并且您有5x10 ^ 6个条目,那么每个子集大约有76个条目;所以创建一个带有空间的文件,比方说,每个子集有100个条目;然后:

  • 在偏移0处开始写入前导4字节0x0000的所有条目;使用0来填充总共100个条目(我认为1700个字节);

  • 在偏移1700处开始写入前导4个字节的所有条目0x0001,pad,

  • 重复,直到您写完所有数据。

现在,您的查找将成为计算,以计算文件中的偏移量,然后扫描最多100个条目以找到您想要的那个。如果这还不够快,则使用16 ^ 5个子集,每个子​​集允许大约6个条目(6x16 ^ 5 = 6291456)。我想这会比二分搜索更快 - 但这只是猜测。

插入是一个问题,由您掌握数据知识决定新条目(a)是否需要重新排序子集,或者(b)可以简单地添加到数据库末尾。该索引处的条目列表(这意味着在每次查找时扫描整个子集)。

如果空间非常重要,您当然可以从条目中删除前4个字节,因为它们是通过计算文件偏移量来计算的。

我所描述的,非常不错,是哈希表

答案 5 :(得分:1)

您的密钥是128位,但如果您有最多10 ^ 7个条目,则只需要24位来对其进行索引。

  1. 您可以制作哈希表,或

  2. 使用Bentley风格的展开式二进制搜索(最多24次比较),如

  3. 这是展开的循环(具有32位整数)。

    int key[4];
    int a[1<<24][4];
    
    #define COMPARE(key, i) (key[0]>=a[i][0] && key[1]>=a[i][1] && key[2]>=a[i][2] && key[3]>=a[i][3])
    
    i = 0;
    if (COMPARE(key, (i+(1<<23))) >= 0) i += (1<<23);
    if (COMPARE(key, (i+(1<<22))) >= 0) i += (1<<22);
    if (COMPARE(key, (i+(1<<21))) >= 0) i += (1<<21);
    ...
    if (COMPARE(key, (i+(1<<3))) >= 0) i += (1<<3);
    if (COMPARE(key, (i+(1<<2))) >= 0) i += (1<<2);
    if (COMPARE(key, (i+(1<<1))) >= 0) i += (1<<3);