实现固定大小的哈希映射

时间:2015-10-26 04:54:58

标签: java c hash hashmap hashtable

我需要实现针对内存和速度优化的固定大小的哈希映射,但目前还不清楚这意味着什么:这是否意味着我在哈希映射中可以拥有的桶数是固定的?或者我无法动态分配内存并扩展哈希映射的大小以通过链接列表解决冲突?如果对后一个问题的答案是肯定的,那么首先想到的碰撞解决方法是线性探测 - 任何人都可以评论其他更多的内存和速度有效的方法,还是指向任何资源开始?谢谢!

1 个答案:

答案 0 :(得分:0)

在没有看到具体要求的情况下,很难解释“针对内存和速度优化的固定大小哈希映射”的含义,因此我将专注于“针对内存和速度进行优化”方面。

内存

如果哈希映射实际上以“固定”大小存在,则内存效率很难给出建议。通常,open-addressing可以提高内存效率,因为可以存储密钥和值,而无需指向下一个和/或前一个链表节点的指针。如果允许您的哈希映射调整大小,您将需要选择一个冲突解决策略,在调整大小之前允许更大的负载(元素/容量)。 1/2是许多哈希映射实现使用的常见负载,但它意味着至少2x始终使用必要的内存。冲突解决策略通常需要在速度和内存效率之间取得平衡,特别是针对您的实际需求/用例进行调整。

速度

从现实世界的角度来看,特别是对于较小的哈希映射或具有简单键大小的哈希映射,优化速度的最重要方面可能是减少cache-misses。这意味着尽可能多地放置在连续内存空间中执行操作所需的信息。

我的建议是使用open-addressing代替chaining来解决冲突。这将允许您的更多内存是连续的,并且每个密钥比较应该至少减少1个缓存未命中。开放寻址将需要某种类型的探测,但与从内存循环获取链接列表的每个链接的成本相比,检查密钥比较应该更快。有关c ++ std :: vector vs std :: list的基准测试,请参阅here,对于大多数操作而言,正常的连续数组由于空间局部性而尽管算法复杂,但速度更快。

就探测类型而言,线性探测存在聚类问题。当碰撞发生时,相邻的元素被消耗,这导致在阵列的相同部分中发生越来越多的碰撞,当表几乎满时,这变得异常重要。这可以通过重新散列来解决Robin-Hood hashing(当你想要插入时,如果你到达一个比它被插入的元素更接近它的理想插槽的元素,交换这两个并尝试插入该元素,A可以看到更好的描述here)等。二次探测不具有线性探测所具有的聚类问题,但它有自己的局限性,并非每个阵列位置都能到达每个阵列位置,因此依赖于大小,通常只有一半的数组可以填充,然​​后才需要调整大小。

数组的大小也会影响性能。最常见的尺寸是2种尺寸的功率和素数尺寸。 Java: A "prime" number or a "power of two" as HashMap size? 两者都存在参数,但主要是性能取决于使用情况,特别是两个大小的函数通常非常坏,并且哈希是顺序的,但是哈希到数组索引的转换可以用一个{ {1}}操作与相对昂贵的and

值得注意的是,google_dense_hash是一个非常好的哈希映射,几乎在每个用例中都很容易超越c ++标准库变体,并使用开放寻址,2调整大小约定和二次探测的强大功能。在许多情况下,Malte Skarupke写了一个优秀的哈希表来攻击google_dense_hash,包括查找。他的实现使用知更鸟罩散列和线性探测,具有探头长度限制。它在blog post中得到了很好的描述,同时还有针对其他哈希表的优秀基准测试以及对性能提升的描述。