哈希表的大小是固定的
...不一定 - 哈希表可以支持调整大小,但这往往是在相当戏剧性和侵入性的块中完成的,您可以在其中推断哈希表,就好像它在之前和之后都是恒定大小一样。
...等于其哈希函数输出大小*键值对大小*桶大小+溢出桶大小中可存储的最大值。例如,如果散列函数产生 16 位散列并且存储桶大小为 4 且值为 32 位,那么它将是 2^16 * 4 * 6 = 1572864 或 1.5MB 加上溢出。
一点也不。计算大小的更好方法是说有 N 个特定大小的值,并且您希望将容量:大小比率保持在 3:1 和 5:4 之间:表内存使用量为:N * sizeof(Value ) * 比率。
散列值中的位数仅表示可以散列到的不同存储桶的最大数量:如果您尝试使用更大的表,那么您将获得比散列更多的冲突生成更宽位哈希值的函数。如果您的散列函数中的位比您需要的多这不是问题,例如取当前表大小的模数以找到您的存储桶:hashed_to_bucket = hash_value % num_buckets。
这实质上会使哈希表成为一种压缩查找表。
这是查看哈希表的好方法。
如果散列函数发生变化,则必须重新评估整个表。否则它只会向空槽添加东西。
肯定会重新评估/重新生成。否则添加到空槽只是不良后果之一。
此外,哈希表可以包含其哈希大小可以处理的最大单位(因此对于 16 位哈希,其为 65536),但要在没有很多冲突的情况下表现良好,它必须少得多。
如上所述,这(例如 65536)不是 hard 最大值,但应该避免“在没有冲突的情况下表现良好”。为了表现良好,不必要少得多:只要是高质量的 16 位散列函数,最高 65536 的任何东西都可以。
好的,这就是我要索引的内容:(最多)1 亿对,带有 64 位整数键和 96 位值。键是对象 ID(主要以短序列出现,但可能无处不在),值是对象位置 + 长度。读/写同样重要且非常频繁。
我研究的其他选项是各种树,但我不喜欢它们的原因是因为在我看来,我必须进行大量稀疏读/写来查找数据或重构树每次进去。
可能...很大程度上取决于您的访问模式。例如,如果您碰巧尝试按照“短序列”访问密钥,那么倾向于将它们放在内存/磁盘附近的数据组织模型会有所帮助。某些类型的树结构可以很好地做到这一点,有时您也可以破解您的哈希函数来做到这一点(但需要平衡它与碰撞倾向)。
在我看来,我需要一个包含奇怪位数的散列,我想最多 38 位,因为它几乎是我可以存储在单个磁盘上的最大值,并且应该足够舒适1亿。奇怪的比特量是闻所未闻的吗?我想我可能会在 CPU 之前出现磁盘活动瓶颈。
不是这样...你有 64 位整数键 - 一个 64 位或更大的散列将是可取的。也就是说,一个 32 位散列也可能很好——它会生成 40 亿个不同的值,这比你的 1 亿个键还多。
有没有关于如何为我的特殊情况设计一个好的散列函数的文章?谷歌搜索给了我常用方法的概述,但我正在寻找它们背后的解释。
我不知道。
我应该知道的任何其他一般提示/陷阱?
对于提示...我会说从简单开始(例如,哈希函数返回键不变并使用具有质数的哈希表容量的模数,或者如果您要获取哈希,则使用任何常见的哈希使用例如 2 次幂的存储桶数的表实现)并测量您的冲突率:这告诉您在改进您的散列方面值得付出多少努力。
在您的情况下获得“理想的、随机化”散列的一种非常简单的方法是拥有 8 个 256 个 32 位整数的表 - 使用硬编码的随机数进行初始化(您可以在 google 上搜索随机数下载网站)。给定任何 64 位密钥,只需将其切成 8 个字节,然后将每个字节用作连续表中的密钥,对您查找的 32 位值进行异或运算。 64 个输入位中任何一个位的差异都会以相等的概率影响哈希值中的所有 32 位。
uint32_t table[8][256] = { ...add some random numbers... };
uint32_t h(uint64_t n)
{
uint32_t result = 0;
unsigned char* p = (unsigned char*)&n;
for (int i = 0; i < 8; ++i)
result ^= table[i][*p++];
return result;
}