记录一下哈希表底层原理

理解HashMap底层,首先应该理解Hash函数

从解决一个问题入手:大量的数据要存储查询,构造哈希表来解决

初步想法

借鉴数组下标访问的思路来做,只需知道起始位置和下标值,

不管数组中有多少个元素,都可以一次访问到,

将元素和元素位置建立一种一一对应的关系

Hash函数的出现

输入的元素的范围可能很大甚至无穷,而我们的内存有限,

所以说我们需要一种函数映射关系,将这些无限的元素映射到我们有限的内存地址上。

Hash函数代表着一类函数,即把任意范围的元素可以通过映射关系压缩成固定范围的元素。

Hash函数的选择

如果是正整数,我们可以用这个正整数数除以某个数,取其余数,即我们常用的 k % m,k为正整数,m 为除数;

这样一来,范围就缩小了很多,比如说 15%10=5,26%10=6,...,所有的正整数经过运算,都变成了 0-9 范围之间的数了,

这样范围就缩小了很多

m 的选择

这种做法,m 的选择就非常重要了,如果 k 值分布均匀还无所谓,如果 k 值具有某些特征

比如说 k 的个位基本上不变,而高位分布均匀,如 15,25,45,65,85,95,155,就遭遇大冲突了,

必须要使得经过Hash函数后关键字的分布均匀,尽量减少冲突

链地址法

为了解决冲突,引出链地址法

在存储的时候,如果多个元素被Hash到同一位置,那么就加入到该位置所指向的链表中,

如果该位置没有元素,则为null(指向空)”

由于新加入的元素很可能被再次访问到,使用“头插”

rehash

这样解决冲突固然好,但是也有瓶颈

当我们实际存入的值越来越多的时候,这个链表也势必越来越长,

那当我们进行查找的时候,势必就会遍历链表,效率也就越来越慢。

因此,我们要选取一个相关的新的Hash函数(比如之前使用 key % m,现在只改变一下m的值)

将旧Hash表中所有的元素通过新的Hash函数计算出新的Hash值,并将其插入到新表中(仍然使用链表),这就叫rehash

 这里的数组就扩大了近两倍,由于要大小要选素数,那就选原数组大小两倍后的第一个素数7,旧Hash表和新Hash表采用了不同的Hash函数,但相关,只是m的取值变了

装载因子 α 

 我们可以定义这样一个变量 α = 所有元素个数/数组的大小,

它代表着我们的Hash表(也就是数组)的装满程度,在这里也代表链表的平均长度

 这个装载因子代表了Hash表的装满程度,这里也可以代表链表的平均长度,那么也就可以代表查询时的时间长短了。

 

参考资料:神速哈希上神速哈希下

转载于:https://www.cnblogs.com/kumata/p/9857341.html

### 哈希函数的底层实现原理 哈希函数的核心目标是将任意长度的数据映射到固定大小的空间中,从而使得查找操作能够在常数时间内完成。其工作流程通常分为以下几个方面: #### 1. 转换过程 通过特定的数学公式或逻辑运算,将输入数据转化为一个整数值作为索引位置。这一过程中常用的算法有霍纳法则(Horner's Rule)、乘法散列法等[^3]。 #### 2. 取模运算 为了确保生成的结果适合存储数组的实际容量范围,会对上述得到的大整数执行取余操作 (`%`) ,最终得出具体的位置编号用于存取键值对中的元素。 ```javascript // 示例代码展示如何利用简单的字符串处理构建基本版hash funciton. function simpleHashFunction(str, size){ let hashValue = 0; for(let char of str){ hashValue += char.charCodeAt(); } return hashValue % size; } console.log(simpleHashFunction("hello", 8)); // 输出某个介于0至7之间的数字代桶号 ``` 此段JavaScript程序片段展示了最基础形式下的字符串转码成对应bucket index的方法之一——累加字符编码再做除以预设容器尺寸后的剩余部分即为目标地址;当然实际应用当中会采用更复杂精密的设计来优化分布均匀度并降低碰撞几率。 --- ### 数据结构算法哈希表的工作机制 #### 存储方式 哈希表内部维护了一个动态调整大小的一维数组 `storage` 和记录当前已占用槽位数量变量 `count`,以及初始设定的最大装载因子限制参数 `limit`.每当新增项目超出一定比例时触发自动扩展行为以维持整体效率稳定. 当向哈希表插入新条目时,先调用自定义好的hash function依据key值得出相应的目标下标index; 如果该处尚未存在任何东西,则直接放置进去即可完成整个add process;反之遇到collision situation则需采取适当策略解决比如链地址法(separate chaining) 或者开放寻址技术(open addressing). ```javascript // 扩展原生HashTable类 添加put方法支持添加KV pair 并考虑冲突情况管理 HashTable.prototype.put=function(key,value){ const idx=this.hashFunc(key,this.limit); if(!this.storage[idx]){ this.storage[idx]=[]; } let bucket=this.storage[idx]; let updated=false; for(var i=0;i<bucket.length;i++){ if(bucket[i][0]==key){ bucket[i]=[key,value]; updated=true; break; } } if(!updated){ bucket.push([key,value]); this.count++; if(this.count/this.limit >= LOAD_FACTOR_THRESHOLD){ this.resize(); } } }; ``` 以上JS代码实现了带有负载均衡考量功能完善的put operation logic,在面对重复keys情形下可以更新旧record而不是创建多余副本浪费空间资源的同时还能适时增大底层数组规模保持良好性能现。 --- ### 总结说明 综上所述可以看出无论是从理论层面探讨还是实践角度出发分析,哈希技术和它背后依托的各种精妙设计都极大地促进了现代计算机科学领域的发展进步。特别是在大数据量级场景下提供了极为高效的检索手段选项。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值