一、哈希表的定义与核心本质
• 别称:散列表,是 键(Key)-值(Value) 映射的高效数据结构
• 核心原理:通过 哈希函数(Hash Function) 将键转换为数组索引,直接访问对应值,实现 O(1) 级别的平均查找、插入、删除效率
• 本质:用空间换时间,通过哈希函数减少查找时的比较次数,是数组查找的优化延伸
二、哈希表的核心组成
- 哈希函数(Hash Function)
◦ 作用:将任意类型的键(字符串、数字等)映射为固定范围的整数(数组索引)
◦ 设计要求:
◦ 确定性:同一键必须映射到同一索引
◦ 高效性:计算过程简单,耗时短
◦ 均匀性:键分布均匀,减少索引冲突
◦ 防碰撞性:尽量避免不同键映射到同一索引
◦ 常见设计方法:
◦ 直接定址法:键→索引直接对应(如键为身份证号,取后几位作为索引)
◦ 除留余数法:index = key % 数组长度(最常用,需选质数作为数组长度)
◦ 数字分析法:提取键中分布均匀的部分作为索引(如手机号后4位)
◦ 折叠法:将键拆分后叠加,取结果作为索引(如长数字拆分成多段求和)
◦ 字符串哈希:将字符ASCII码加权求和后取模(如 hash(s) = (s[0]×31ⁿ⁻¹ + s[1]×31ⁿ⁻² + ... + s[n-1]) % 数组长度)
- 哈希表(数组)
◦ 存储载体:连续的数组空间,每个索引位置称为 桶(Bucket),用于存储键值对
◦ 数组长度选择:通常设为质数,减少除留余数法的冲突概率
- 冲突解决机制
◦ 冲突定义:不同键通过哈希函数得到同一索引(无法避免,只能缓解)
◦ 开放寻址法(闭散列):
◦ 核心:冲突时,按某种规则在数组中寻找下一个空闲桶
◦ 常见方式:
◦ 线性探测:冲突后依次检查下一个索引(index+1, index+2...,易产生聚集)
◦ 二次探测:冲突后按索引±1²、±2²...查找(减少聚集,需数组长度为4k+3型质数)
◦ 双重哈希:用第二个哈希函数计算步长,步长=hash2(key)(分散性最好,避免聚集)
◦ 缺点:数组满时无法插入,删除需标记"已删除"(避免影响后续查找)
◦ 链地址法(开散列):
◦ 核心:每个桶对应一个链表(或红黑树),冲突的键值对依次存入链表中
◦ 优点:冲突处理简单,数组利用率高,删除方便(直接删除链表节点)
◦ 优化:当链表长度超过阈值(如8),转为红黑树(Java HashMap的实现方式),提升查询效率
◦ 其他方法:
◦ 再哈希法:冲突时调用另一个哈希函数重新计算索引
◦ 公共溢出区:将所有冲突的键值对存入单独的溢出数组
三、哈希表的关键性能指标
• 负载因子(Load Factor):α = 存储的键值对数量 / 数组长度
◦ 意义:衡量哈希表的拥挤程度,α越小,冲突概率越低,效率越高
◦ 阈值:通常α阈值设为0.75(Java HashMap默认),超过则触发 扩容(Resizing)
• 扩容机制:
◦ 过程:创建新的更大数组(通常是原长度的2倍,且为质数),重新计算所有键的哈希值并迁移到新数组
◦ 目的:降低负载因子,减少后续冲突,保证效率稳定
四、哈希表的优缺点
- 优点:
◦ 平均时间复杂度:查找、插入、删除均为 O(1),效率远超数组、链表、树
◦ 键值对映射直接,无需遍历,适合高频查找场景
- 缺点:
◦ 最坏时间复杂度:O(n)(哈希函数极差或负载因子过高,导致所有键冲突,链表长度过长)
◦ 空间利用率较低(需预留扩容空间,开放寻址法浪费更多空间)
◦ 无序存储:键值对无固定顺序,无法按索引有序访问
◦ 哈希函数设计难度高:需兼顾均匀性、高效性,否则影响性能
五、常见应用场景
• 数据缓存:如Redis、浏览器缓存(键为URL,值为缓存内容)
• 快速查找:字典查询、用户信息查询(键为用户名/ID,值为用户数据)
• 去重操作:如日志去重、数组去重(利用键的唯一性)
• 键值对存储:数据库索引、哈希映射(HashMap)、哈希集合(HashSet,仅存键)