哈希表(HashMap)数据结构全解析:从哈希码、桶、冲突处理到从零实现

发布时间:2026/11/14 21:52:58

哈希表(HashMap)数据结构全解析:从哈希码、桶、冲突处理到从零实现 哈希表HashMap数据结构全解析从哈希码、桶、冲突处理到从零实现【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum本篇技术指南以本仓库中 hash_map_data_structure.md 为核心脉络系统讲解哈希表Hash Map / Hash Table这一编程语言中最常用数据结构之一的内部工作原理什么是哈希码、桶Bucket与键值对如何存取、冲突Collision为何必然发生以及如何用链表和质数哈希缓解、容量与负载因子Load Factor如何驱动扩容。在理解原理之后你还将结合本仓库配套的 project_hash_map.md 实战指南用 JavaScript 从零实现一个具备hash、set、get、has、remove、keys等完整方法的HashMap类并亲手验证其扩容与均匀分布行为。哈希码Hash Code什么是哈希哈希Hashing的本质是把一个输入值转换成对应的输出值。这个输出值就叫哈希码。哈希函数必须是一个纯函数pure function相同的输入永远产生相同的哈希码过程中不掺杂任何随机成分。以文档中的第一个例子为例一个最简单的哈希函数可以只取姓名的首字母function hash(name) { return name.charAt(0); }把Carlos传入得到哈希码C。这就是一个基础哈希函数。哈希与加密的关键区别不可逆哈希和加密ciphering之间有一条根本分界线可逆性。哈希是单向过程——你可以从姓名得到哈希码却永远无法从哈希码还原出原始姓名。Carlos哈希成C之后你无法断定它原来究竟是Carlos、Carla还是Carrot。正是这种不可逆性让哈希天然适合安全场景见下方哈希的收益对于密码我们可以只保存密码的哈希值而不是明文即使攻击者窃取了哈希数据也无法据此还原出原始密码。使用场景从按首字母归档到数值哈希文档用学校归档的类比来解释哈希的用途一个文件夹系统按姓名首字母组织每个子文件夹存放同首字母学生的档案C: carlos.txt carla.txt B: bryan.txt bob.txt beatrice.txt bella.txt benjamin.txt bianca.txt新来一位名叫Carlos的学生时运行hash(Carlos) - C就能确定他该放入C目录。但这个方案立刻暴露一个问题如果姓 C 开头的人特别多C目录会严重膨胀而其他目录可能空空如也。为了更均匀地分散学生可以改进哈希函数同时纳入名和姓的首字母function hash(name, surname) { return name.charAt(0) surname.charAt(0); }此时Carlos Smith的哈希码是CS学生被分散到更多目录中重复哈希码大量减少但仅靠首字母组合仍然不够——常见姓氏首字母组合仍会造成目录失衡。于是文档进一步把整个名字转换成数字function stringToNumber(string) { let hashCode 0; for (let i 0; i string.length; i) { hashCode string.charCodeAt(i); } return hashCode; } function hash(name, surname) { return stringToNumber(name) stringToNumber(surname); }这里不再只看首字母而是把整个名字的每个字符码值累加为数字。你可能会问为什么不直接把整个名字当作哈希码那样确实能保证唯一但在哈希表的语境下哈希码必须是数字——这个数字将作为桶的索引用来定位键值对的存储位置。桶Bucket哈希表如何存取键值对桶是存储元素的容器。可以把数组的每个下标都视为一个桶。对于某个具体的键哈希函数返回一个数字作为数组下标键值对就存储在该下标对应的桶中。以存储键Fred、值Smith为例文档给出了完整流程将Fred传入哈希函数得到哈希码385找到下标为385的桶把键值对存入该桶键是Fred值是Smith。如果下标385的桶里已经有一个键同为Fred的条目则比较键是否相同相同则用新值覆盖旧值。这正是Set能保证元素唯一的原因——Set与哈希表类似只不过它的节点只含键、不含值。按键取值时流程同样清晰哈希键并计算出它的桶下标如果桶不为空则进入该桶比较桶内节点的键是否与查询键一致一致则返回节点值否则返回null。为什么已经通过哈希码定位到桶还要再比较键因为哈希码只是位置不同的键可能生成相同的哈希码即碰撞所以必须用键来确认桶内的条目是否就是要找的那个。基于这套机制一个最简哈希表只需实现has、set、get三个方法即可工作。注意哈希表不保证插入顺序哈希表在迭代时不保证插入顺序。哈希码到下标之间的映射并非从第一个下标到最后一个下标的线性推进而是不可预测的与插入次序无关。也就是说如果你取出全部键或值来迭代它们的顺序不会是你插入的顺序。例如依次插入Mao、Zach、Xari迭代时却可能得到[Zach, Mao, Xari]。某些库如 JavaScript 原生的Map会实现为保留插入顺序但本仓库后续项目实现的是无序哈希表。如果你需要频繁地按顺序迭代数组才是更合适的选择。碰撞Collision哈希表无法回避的问题碰撞指两个不同的键生成了完全相同的哈希码从而落在同一个桶中。例如Sara和raSa由相同字母不同排列组成用简单的字符码累加会得到相同结果。解决办法是重写stringToNumber让哈希码依赖字母在字符串中的位置function stringToNumber(string) { let hashCode 0; const primeNumber 31; for (let i 0; i string.length; i) { hashCode primeNumber * hashCode string.charCodeAt(i); } return hashCode; }新的函数让Sara与raSa产生不同哈希码虽然字母相同但位置不同每次迭代都用旧哈希乘以 31 再加上当前字符码位置信息被放进了哈希码。为什么要用质数Prime Number作乘数注意这里选用了质数31。我们本可以选任何数字但质数更优乘以质数会降低哈希码能被桶数量整除的可能性从而帮助减少碰撞的发生。这是哈希函数设计中一个广为人知的工程经验。碰撞无法彻底消除即使重写了哈希函数碰撞仍无法根除——桶的数量是有限的而键可以是无穷的。所以我们只能尽量最小化碰撞而非消除碰撞。当节点数超过桶数时碰撞在数学上是被保证的即鸽笼原理。用链表处理碰撞到这一步为止哈希表还只是一维的数据结构。处理碰撞的思路是让每个桶里的Node能存多个值——引入链表Linked List。每个桶都变成一条链表插入时若桶为空则插入链表头节点若桶中已有头节点则沿链表追加到末尾。至此你应该明白为什么要写一个尽可能消除碰撞的哈希函数。实际开发中绝大多数语言都内置了哈希函数很少需要自己编写但理解其原理仍然至关重要。链表的基础概念与操作可参考本仓库的 project_linked_lists.md链表是由节点node通过指针串联的线性集合头节点head是第一个节点尾节点tail是最后一个节点。哈希表的增长Growth容量与负载因子桶的数量不能无限大内存有限但也不能一开始就开太大若哈希表里只有一条数据纯属浪费。文档给出的折中方案是从一个大小为16的小数组开始。为什么是16因为它是 2 的幂可以配合某些依赖位运算bit manipulation的索引性能优化技巧。大多数编程语言默认桶数就是16。用取模把大哈希码压缩进桶范围哈希函数可能产生像20353924这样的大数字如何映射到桶答案是取模运算%任意数字对16取模结果必在0到15之间。例如要确定值Manon落入哪个桶随着节点不断插入碰撞概率持续上升当节点数超过桶数时碰撞必然发生。理想状态下每个桶要么 0 个节点要么 1 个节点因此当负载变高时需要把桶数组扩大一倍并将所有已有节点重新哈希后复制到新数组的桶中。何时扩容capacity × load factor哈希表需要跟踪两个字段来决定扩容时机capacity容量当前桶的总数load factor负载因子初始化时分配给哈希表的数值用于判定扩容时机。不同语言实现通常取值在0.75到1之间。两者的乘积就是一个阈值当哈希表中的条目数超过该乘积时触发扩容。例如有16个桶、负载因子0.8则阈值是16 * 0.8 12.8——第13个条目插入时就要扩容。负载因子设得太低会因空桶过多而浪费内存设得太高则会在扩容前允许桶内累积大量碰撞。这是一个典型的空间-时间权衡。计算复杂度Computation Complexity哈希表在插入、检索、删除上都极其高效因为这三个操作都直接借助数组下标完成。假设实现良好以下方法平均复杂度为O(1)插入Insertion检索Retrieval删除Removal最坏情况下上述操作退化为O(n)当所有数据都被哈希到同一个桶时复杂度来自链表——需要遍历链表才能把又一个节点插入同一个桶这正是碰撞造成的。哈希表的扩容growth则始终是O(n)因为要重新哈希并复制全部节点。实战用 JavaScript 从零实现自己的 HashMap理解了全部原理之后本仓库的配套项目 project_hash_map.md 要求你亲手实现一个HashMap。以下是该项目的核心约束与设计要点。边界限制阻止越界访问JavaScript 数组的动态性允许读写超出数组长度的下标——例如创建大小为16的桶数组后依然可以往下标500存入数据。这破坏了哈希表限制存储规模的设计初衷因此每次通过下标访问桶时都应加上边界检查if (index 0 || index buckets.length) { throw new Error(Trying to access index out of bounds); }类结构与方法清单创建HashMap类或工厂函数至少维护load factor与capacity两个变量load factor取0.75初始capacity为16。需要实现的方法如下hash(key)接收字符串键并生成哈希码。文档提供了此前课程中实现的较优版本function hash(key) { let hashCode 0; const primeNumber 31; for (let i 0; i key.length; i) { hashCode primeNumber * hashCode key.charCodeAt(i); } return hashCode; }你可以直接使用它也可以自行研究其他哈希算法这是一个很深的领域。要点如下返回前必须用%对哈希码取当前容量的模确保下标始终落在桶数组范围内无论后续如何扩容长键风险键很长时累乘可能超过Number.MAX_SAFE_INTEGER导致计算失真。稳妥做法是在循环的每一次迭代内就对当前容量取模而不是等循环结束后只取一次模键与哈希码不要混淆键只是hash函数的输入我们从不直接用键访问桶而是始终通过哈希码访问。set(key, value)接收键与值两个参数。若键已存在则覆盖旧值视为更新。注意区分更新与碰撞Rama和Sita哈希后都落在下标3因为键不同这是碰撞而非更新。碰撞处理参照上文用链表处理碰撞一节。扩容逻辑与set紧密相关——应在桶被填满达到负载因子时正好触发扩容因此建议把扩容功能放在较后实现但要意识到它由set驱动。get(key)接收键返回其对应的值键不存在则返回null。has(key)接收键返回true或false表示键是否存在于哈希表中。remove(key)接收键若键存在则删除该条目并返回true不存在则返回false。length()返回哈希表中已存储键的数量。clear()清空哈希表中的所有条目。keys()返回包含全部键的数组。values()返回包含全部值的数组。entries()返回包含每个key, value对的数组形如[[firstKey, firstValue], [secondKey, secondValue]]。再次提醒哈希表不保留插入顺序keys()、values()、entries()的结果乱序是正常且符合预期的。验证你的实现触发扩容并检查均匀分布项目文档给出了完整的验证脚本。创建一个 JavaScript 文件实例化哈希表负载因子0.75const test new HashMap() // or HashMap() if using a factory依次用set填充 12 个条目test.set(apple, red) test.set(banana, yellow) test.set(carrot, orange) test.set(dog, brown) test.set(elephant, gray) test.set(frog, green) test.set(grape, purple) test.set(hat, black) test.set(ice cream, white) test.set(jacket, blue) test.set(kite, pink) test.set(lion, golden)此时负载水平恰好达到0.75满容量。接下来用set覆盖若干节点的值——这只应更新已有节点的值而不新增节点因此length()不变、capacity不变。随后插入最后一个节点test.set(moon, silver)这会令负载超过负载因子触发扩容capacity翻倍。若实现正确扩容后负载水平应远低于负载因子且条目在扩容后的桶中均匀分布。最后再覆盖几个节点的值仍只更新不新增并逐一测试get、has、remove、length、clear、keys、values、entries在扩容后是否依然工作正常。额外挑战Extra Credit实现一个HashSet类或工厂函数行为与HashMap一致但只含键、不含值。小结与知识自检通过本文你已经掌握了哈希表的完整知识链条哈希码的生成与不可逆性 → 桶的下标定位与键值对存取 → 碰撞的成因鸽笼原理保证必然存在与链表达化解法 → 质数乘数对分布的改善 → 容量与负载因子驱动的 O(n) 扩容 → 平均 O(1)、最坏 O(n) 的复杂度画像并能够参照本仓库的 project_hash_map.md 从零实现一个功能完整的HashMap。理解哈希函数工作原理远比记住某个语言的现成 API 更重要——它是你日后评估各种哈希类数据结构Map、Set、对象字面量性能与行为的基础。你可以用下面四个问题检验自己的理解哈希hash意味着什么什么是桶buckets什么是碰撞collision何时是扩容桶数组的最佳时机【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/11/7 4:08:59

SpringBoot校园网上店铺系统开发实践

1. 项目背景与核心需求校园网上店铺系统是当前高校数字化建设的重要组成部分。作为一名在校园信息化领域深耕多年的开发者,我发现传统线下店铺模式存在营业时间受限、管理效率低下、学生消费体验差等痛点。基于SpringBoot的网上店铺系统能够有效解决这些问题&#x…

2026/11/14 21:39:44

电力电缆考试多久下证 全国通用证书怎么复审不掉队

电力电缆考试多久下证 全国通用证书怎么复审不掉队 证书过期了不知道怎么复审延期,你是不是也遇到过这种情况?别急,咱们来聊聊 电力电缆考试多久下证 的问题,以及这个 全国通用 的证书怎么管理、怎么复审,避免因为证书过期影响你的工作和收入。…

2026/11/14 21:39:44

电工证高压抵押证丢了怎么补办保姆级教程

电工证高压抵押证丢了怎么补办保姆级教程 证书过期了不知道怎么复审延期?你不是一个人。尤其是在像三门峡水利工程这样的高风险行业,电工证高压抵押证不仅是上岗的“通行证”,更是规避法律责任的“护身符”。一旦证书丢失或过期,不仅影响工作,还可能面临…

2026/11/14 21:39:44

电工证正规培训成都报名费多少钱

成都电工证正规培训真实经历:急需特种作业证才能上岗找工作 我刚从工地下来,手里还带着工具箱的灰尘,可心里却踏实多了,因为 电工证…

2026/11/14 21:39:44

危险特种作业操作证千万别踩坑 过期了怎么复审延期全攻略

危险特种作业操作证千万别踩坑 过期了怎么复审延期全攻略 证书过期了不知道怎么复审延期?这事儿你不是一个人。作为一个在漯河建筑工地干了五年多的电焊工,我深知一旦证书到期不及时处理,不仅影响收入,还可能被清退。很多人像我一样,对危险特种作业操作…

2026/11/13 22:33:32

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/14 0:06:39

土木特种工考证官方报名入口怎么找 之前考的证在外省能不能转过来

土木特种工考证官方报名入口怎么找 之前考的证在外省能不能转过来 之前考的证在外省能不能转过来?这个问题我太熟悉了,作为一名干了十几年的特种作业证书办理从业者,每年都会遇到不少像你一样在外地考了证,回来却发现无法直接使用的学员。别急,今天我就…

2026/11/14 0:06:39

电工证要多久多钱保姆级教程:工地太忙根本没时间复习考试

电工证要多久多钱保姆级教程:工地太忙根本没时间复习考试 工地太忙根本没时间复习考试,你不是一个人。在信阳浉河区,很多刚进入工地的电工都面临这样的困境:工作强度大,时间碎片化,根本没有系统复习考试的机会。但电工证是上岗的硬门槛,…

2026/11/13 22:32:11

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/13 12:14:39

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑