为什么哈希表(字典)的查询速度有时会突然变慢

哈希表(在许多语言中被称为“字典”或“关联数组”)的查询速度,在理想情况下,应是接近“瞬时”的常数时间,然而,在特定场景下,其性能之所以会突然、无征兆地变慢,其根源,在于其底层的“数组+哈希函数”实现机制,在两种关键情况下,会从高效的“直接寻址”模式,退化为低效的“遍历查找”或“大规模数据迁移”模式。导致这种性能“断崖”的五大核心原因涵盖:发生了大量的“哈希冲突”、冲突链表或探测序列变得“过长”、触发了“负载因子”阈值导致了“动态扩容”、底层数组正在进行“大规模”的数据迁移、以及选用了“质量不佳”的哈希函数

其中,触发“负载因子”阈值导致的“动态扩容”,是导致查询(更准确地说是“插入”操作时)“突然”变慢的最主要原因。这意味着,当哈希表内部的“拥挤”程度达到一个临界点时,系统为了维持后续的查询效率,会进行一次“重组”,这个重组过程,需要遍历每一个已存在的元素,并为其重新计算存储位置,对于一个已包含数百万个元素的哈希表而言,这个过程,可能会造成一次肉眼可见的、秒级的“卡顿”。

一、速度的“魔法”:哈希表的“理想”状态

在探讨“为何会变慢”之前,我们必须首先深刻地理解,为何哈希表,在绝大多数情况下,都能够实现那令人惊叹的、近乎“瞬时”的查询速度

1. 核心理念:直接寻址

想象一个储物柜,它有100个柜子,编号从0到99。如果你想存取一个物品,并且,你知道它的柜子编号(例如,58号),那么,你不需要从0号柜子,一个个地找过去,而是可以直接地、一步到位地,走到58号柜子前,进行操作。这个过程,无论储物柜里,已经存放了1个物品,还是99个物品,其花费的时间,都是一样的。

哈希表,正是将这种“直接寻址”的思想,进行工程化实现的、一种天才的数据结构。它在底层,通常,就是一个普通的数组。而实现“直接寻址”的关键,就在于那个被称为“哈希函数”的“魔法”。

2. 哈希函数:从“任意键”到“数组索引”的“翻译官”

哈希函数,是一个数学函数,它的职责,就是接收一个任意格式的“键”(例如,一个字符串"user-id-12345",或一个对象),然后,通过一系列的计算,将其,稳定地、确定性地,转化为一个在底层数组索引范围内的“整数”。

哈希函数("user-id-12345") -> 可能会得到 58

哈希函数("order-id-67890") -> 可能会得到 12

因此,当我们要存入一个“键值对” ("user-id-12345", 用户对象) 时,程序会:

计算"user-id-12345"的哈希值,得到58

然后,直接地,将“用户对象”,存放到内部数组的第58个位置上。

当我们要查询"user-id-12345"时,程序,会重复这个过程,再次计算出58,然后,一步到位地,取出数组第58个位置上的“用户对象”。

3. 理想情况:常数时间的“瞬时”访问

在最理想的情况下,每一个不同的“键”,经过哈希函数的计算,都能得到一个独一无二的“数组索引”。此时,无论是存、取、还是删除,其操作,都只需要进行“一次哈希计算”和“一次内存寻址”,其时间复杂度为常数级别。这意味着,其执行时间,与哈希表中已存储的元素数量,完全无关

二、性能杀手一:“哈希冲突”

然而,“理想” rarely exists in the real world. 哈希表的第一个,也是最核心的性能挑战,就是“哈希冲突”。

1. 什么是哈希冲突?

哈希冲突,是指,两个或多个,完全不同的“键”,在经过了同一个哈希函数的计算后,却不幸地,得到了完全相同的“数组索引”

哈希函数("apple") -> 得到了 66

哈希函数("banana") -> 得到了 88

哈希函数("grape") -> 也不幸地,得到了 66

此时,“apple”和“grape”这两个不同的“键”,就“冲突”在了数组的同一个“坑”上。

2. 冲突为何是“必然”的?

依据数学中的“鸽巢原理”,只要“鸽子”(键)的数量,多于“鸽巢”(数组槽位)的数量,那么,至少有一个“鸽巢”里,会挤着两只或更多的“鸽子”。在哈希表中,即便键的数量,少于数组的槽位数,因为哈希函数分布的随机性,冲突,也依然是高概率会发生的事件。

3. 冲突的解决策略:从“直接入住”到“排队入住”

为了解决冲突,哈希表的实现,必须引入额外的“冲突解决策略”。其中,最常用的一种,被称为“拉链法”。

“拉链法”的核心思想:哈希表底层数组的每一个“槽位”,不再直接地,存储一个“值”,而是存储一个指向“链表”头部的指针。

当冲突发生时

存入("apple", 苹果对象)时,计算出索引66。发现66号槽位为空,于是,创建一个新的链表,将“苹果对象”,作为第一个节点,放入其中。

存入("grape", 葡萄对象)时,再次计算出索引66。发现66号槽位,已经有一个链表了。于是,程序,会在这个链表的末尾,追加一个新的节点,来存放“葡萄对象”。

4. 性能下降的原理

“拉链法”在解决了冲突的同时,也引入了新的“性能瓶颈”。 当我们要查询"grape"时,程序:

计算"grape"的哈希值,得到66,一步,定位到数组的第66个槽位。

然而,在这个槽位上,它发现的,不是一个直接的值,而是一个包含了“苹果”和“葡萄”两个节点的链表

此时,程序,别无选择,只能从头到尾地,对这个小小的“链表”,进行一次“线性遍历”,逐一地,比较每个节点的键,是否等于"grape"

当大量的哈希冲突,发生在同一个或少数几个索引上时,就会导致,这些索引所挂载的“链表”,变得异常地“长”。此时,哈希表的查询,就从原本的、高效的“一次定位”,退化为了“一次定位 + 一次漫长的链表遍历”。在最极端的情况下(即,所有键,都冲突在了同一个索引上),哈希表的性能,将完全退化为与一个普通的、无序的链表,完全相同,其查询的时间复杂度,也从常数级别,下降到了线性级别

三、性能杀手二:“动态扩容”

如果说“哈希冲突”,是导致哈希表性能“缓慢”下降的“慢性病”,那么,“动态扩容”,则是导致其性能“突然”卡顿一下的“急性病”。

1. 负载因子:哈希表的“拥挤度”

为了避免因哈希冲突过于频繁,而导致的性能严重下降,哈希表的实现中,引入了一个关键的监控指标——“负载因子”。

负载因子 = 已存储的元素数量 / 底层数组的总槽位数

它度量了哈希表的“拥挤程度”。一个负载因子为0.8的哈希表,意味着其80%的槽位,都已经被占用了。

2. “扩容”的触发

当哈希表的“负载因子”,超过了一个预设的“阈值”时(在Java的哈希映射实现中,这个阈值,默认是0.75),“动态扩容”机制,就会被触发。系统认为,当前的“房间”,已经太拥挤了,如果不立即“扩建”,后续新来的“客人”,发生“冲突”的概率,将急剧增加。

3. “重新哈希”的昂贵过程

“动态扩容”,并非一次简单的内存追加,而是一次极其昂贵的、全局性的“数据大迁徙”,这个过程,通常被称为“重新哈希”。其具体步骤如下:

创建一个新的、更大的底层数组(通常,是旧数组容量的两倍)。

遍历旧数组中的“每一个”已存在的元素

对于每一个元素,必须,依据“新”的、更大的数组容量,重新地,计算一次它的哈希值,以得到它在“新家”中的“新位置”。(因为,哈希值,通常,都与数组的容量,相关)。

将这个元素,插入到新数组的、那个被重新计算出的位置上。

4. “突然变慢”的时刻

这个“重新哈希”的过程,其耗时,与哈希表中已存在的元素数量,成“线性”关系

如果一个哈希表中,已经存储了一百万个元素,那么,在第一百万零一个元素,被插入的那一刻,如果恰好,触发了“负载因子”的阈值,那么,程序,就需要暂停下来,去完整地,执行一次对这一百万个元素的“大迁徙”。

这个过程,可能会耗时数十毫秒,甚至数秒。对于一个需要进行实时交互的应用而言,这种“突然的、无征兆的、长时间的卡顿”,其体验,是灾难性的。

而一旦这次昂贵的“扩容”完成后,哈希表的“负载因子”会大幅下降,后续的查询和插入,又会恢复到“瞬时”的速度。

四、在实践中“规避”与“优化”

理解了上述两大核心原理后,我们就可以在实践中,采取一系列的策略,来规避和优化这些性能问题。

为哈希表“预设容量”如果,你在使用一个哈希表之前,已经能够,大致地,预估出,你将要向其中,存入多少个元素,那么,在创建它的时候,就明确地,为其,指定一个足够大的“初始容量”,是一个极其有效的优化手段

例如,在Java中,new HashMap<>(initialCapacity)

通过“一步到位”地,为其,分配一个足够大的“房子”,我们就可以,从根本上,避免在后续的使用过程中,因为“房间不够住”,而反复地,进行那数次昂贵的“扩建”工程。

选择合适的“键”与哈希函数

尽可能地,使用“不可变”的对象(如字符串、数字)作为键。

如果你需要,使用自定义的对象作为键,那么,你必须,为这个对象,精心设计一个高质量的、能够让哈希值,尽可能“均匀分布”的哈希函数

在流程中管理“性能”预期 性能,是一种重要的“非功能性需求”。

在进行需求分析和技术方案设计时,就应将“预期的数据规模”,作为一个关键的考量因素。

PingCode 这样的研发管理平台中,可以将性能指标(例如,“在100万用户数据规模下,用户信息的查询响应时间,应在50毫秒以内”),作为明确的、可被测试的“验收标准”,写入相关的需求工作项中。

对于一些大型的数据处理或迁移项目,可以在 Worktile项目计划中,明确地,设立专门的“性能测试与调优”任务阶段。

常见问答 (FAQ)

Q1: “哈希表”和“字典”、“关联数组”是一回事吗?

A1: 它们,在概念上,指的是同一种,基于“键值对”进行存储和访问的抽象数据类型。而“哈希表”,则是这种抽象数据类型,最常见、最高效的“一种底层实现方式”。在Python中,它被称为“字典”;在JavaScript中,它被称为“对象”或“映射”;在Java中,则被称为“哈希映射”。

Q2: 既然会变慢,为什么哈希表还是被如此广泛地使用?

A2: 因为,在绝大多数的、平均的情况下,其接近“常数时间”的查询效率,远胜于其他任何一种数据结构(如列表或树)。其“变慢”的场景(即哈希冲突严重或动态扩容),是可以通过良好的设计(如预设容量、使用好的哈希函数),在很大程度上,被规避摊销的。

Q3: 我应该如何为我的哈希表,选择一个合适的“初始容量”?

A3: 一个常见的经验法则是,将你的“预期元素数量”,除以“默认的负载因子”(通常是0.75),然后再向上,取一个最接近的、2的幂次的数。例如,如果你预期,要存入10000个元素,那么一个合理的初始容量,可以是 10000 / 0.75 ≈ 13333,向上取整到2的幂次,即16384

Q4: 什么是“完美的哈希函数”?它在现实中存在吗?

A4: “完美的哈希函数”,是指能够,将一个已知的、固定的键集合,一一地,映射到一组连续的整数(即,完全没有哈希冲突)的函数。它在现实中,是存在的,但其应用场景,非常有限,只适用于那些“键集合,是完全固定不变的”的特殊情况。对于常规的、动态变化的哈希表,我们追求的,是一个能够让哈希值“尽可能均匀分布”的、“足够好”的哈希函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93141.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93141.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

whisper 语种检测学习笔记

目录 transformers推理&#xff1a; transformers 源代码 网上的语种检测调用例子&#xff1a; 语种检测 api transformers推理&#xff1a; https://github.com/openai/whisper/blob/c0d2f624c09dc18e709e37c2ad90c039a4eb72a2/whisper/decoding.py waveform, sample_rat…

第1节 从函数到神经网络:AI思路的逆袭之路

&#x1f914; 开篇灵魂拷问 是不是觉得AI知识体系庞大到吓人&#xff1f;看了一堆快餐视频还是云里雾里&#xff1f;别慌&#xff01;这个系列就是要帮你打通任督二脉&#xff0c;用"既快又慢、既深入又肤浅、既有趣又严肃"的方式讲透AI基础知识&#xff01; &…

【科研绘图系列】R语言绘制多种饼图

文章目录 介绍 加载R包 数据下载 导入数据 数据预处理 画图1 画图2 画图3 画图4 画图5 画图6 系统信息 参考 介绍 【科研绘图系列】R语言绘制多种饼图 加载R包 rm(list = ls()) library(ggstatsplot) library(ggplot2) library(plotrix) library(ggpubr

vue3权限树封装成组件

vue3权限树组件 功能&#xff1a; 1、勾选节点、自动把父节点勾选。 2、取消勾选、子节点全部取消勾选。检查父节点&#xff0c;如果只有这个子节点、遍历把父节点取消勾选 3、filter过滤不仅展示父节点、相关子节点同时展示 4、 高亮显示所有过滤数据 效果图父组件引用 <te…

铨林接纸机学习记录1

光电开关学习做保养也是检查这些东西&#xff0c;包括气路有没漏气&#xff0c;固定件松动、轨道清洁之内刀座暂停光电I23刀座行程磁性开关&#xff0c;这个是安全警戒光电&#xff0c;驱动侧发射信号&#xff0c;操作侧接收刀座暂停光电正常运行是空白的&#xff0c;当出现遮挡…

47.分布式事务理论

所有的事务都必须满足ACID的原则: 原子性:事务中的所有操作,要么全部成功,要么全部失败。 一致性:要保证数据库内部完整性约束、声明性约束。 持久性:对数据库做的一切修改将永久保存,不管是否出现故障。 隔离性:对同一资源操作的事务不能同时发生。 分布式事务的…

【软考】进度管理知识库工具-挺方便

进度管理知识库 全面解析项目管理中的进度管理核心概念、工具、技术和最佳实践&#xff0c;帮助您高效管理项目时间线 六步流程法 规划进度管理 - 制定进度管理计划 定义活动 - 识别和记录项目活动 排列活动顺序 - 确定活动间的逻辑关系 估算活动持续时间 - 估算完成单项活动所…

PDF Replacer:高效便捷的PDF文档内容替换专家

在日常工作和学习中&#xff0c;PDF文件因其格式稳定、兼容性强而被广泛使用。然而&#xff0c;PDF文件的编辑和修改往往比其他文档格式更加复杂。PDF Replacer正是为了解决这一痛点而设计的&#xff0c;它是一款方便实用的PDF文档替换工具&#xff0c;能够帮助用户快速替换PDF…

Java中MybatisPlus使用多线程多数据源失效

Java中MybatisPlus使用多线程多数据源失效 文章目录Java中MybatisPlus使用多线程多数据源失效一&#xff1a;背景二&#xff1a;解决方法三&#xff1a;其他导致DS失效的条件3.1、Transactional一&#xff1a;背景 Mybatis-Plus使用异步任务后不能找到指定设置的DS数据库&…

机器翻译:模型微调(Fine-tuning)与调优详解

文章目录一、模型微调&#xff08;Fine-tuning&#xff09;概述1.1 模型微调是什么&#xff1f;1.2 为什么需要微调&#xff1f;1.3 微调的核心步骤1.4 选择微调策略1.5 训练与优化1.6 微调 vs. 从头训练&#xff08;From Scratch&#xff09;1.7 微调工具推荐二、模型调优&…

如何使用 AI 大语言模型解决生活中的实际小事情?

在 AI 技术飞速发展的今天&#xff0c;大语言模型早已不是实验室里的 “黑科技”&#xff0c;而是能实实在在融入日常生活的实用工具。从日常琐事处理到学习工作辅助&#xff0c;只需掌握简单的使用技巧&#xff0c;就能让 AI 成为你的 “生活小助手”。本文将通过具体场景案例…

佰力博检测与您探讨低温条件下如何测介电性能

在低温条件下测量介电性能时&#xff0c;需要综合考虑温度控制、样品制备、测试设备和测量方法等多个方面。1.温度控制与降温方法1.低温测试中&#xff0c;温度的精确控制是关键。低温测试通常采用液氮或液氮泵进行降温&#xff0c;以达到极低温度&#xff08;如-196C&#xff…

大规模分布式光伏并网后对电力系统的影响

光伏发电作为一种清洁、可再生的能源&#xff0c;正融入我们的电力系统&#xff0c;但是&#xff0c;随着新能源的发展&#xff0c;光伏发电的大规模并网&#xff0c;也给电网的稳定运行带来了新的挑战。下面小编将从四个方面&#xff0c;分别论述光伏并网对电网的影响以及如何…

LeetCode热题100--146.LRU缓存--中等

1. 题目 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;否则…

机器学习学习总结

一、机器学习到底是什么&#xff1f; 简单说&#xff0c;机器学习就是让计算机像人一样 “从经验中学习”。比如我们学骑自行车&#xff0c;摔多了就知道怎么保持平衡&#xff1b;计算机处理任务时&#xff0c;也能通过分析大量 “经验数据”&#xff0c;自己找到规律&#xff…

Boost库中boost::function函数使用详解

1. 函数作用 boost::function 是 Boost 库提供的一个 通用函数封装器&#xff0c;可用于存储、传递和调用任意可调用对象&#xff08;如普通函数、函数指针、Lambda、函数对象、成员函数指针等&#xff09;。它类似于 C11 及以上标准的 std::function。 作用总结&#xff1a; 可…

SQL Server安全删除数据并释放空间的技术方案

在SQL Server中执行大规模数据删除时&#xff0c;直接使用DELETE语句可能导致日志文件暴涨、事务阻塞和性能下降。以下提供一种安全删除数据并释放磁盘空间的完整方案&#xff1a; 方案核心步骤 -- 设置读未提交隔离级别&#xff08;避免锁竞争&#xff09; SET TRAN ISOLATION…

EgoVLA——根据第一视角的人类视频中训练的VLA模型:助力家具组装等人形灵巧操作任务的攻克(利用可穿戴手部追踪)

前言 我在此文《ForceVLA——将具备力感知的MoE整合进π0的动作专家中&#xff1a;从而融合“视觉 语言 力反馈”三者实现精密插拔》的开头说过&#xff0c;我司「七月在线」目前侧重以下两大本体的场景落地 人形层面&#xff0c;侧重 1.1 人形灵巧操作 1.2 人形展厅讲解机械…

厨具新风尚,解锁厨房新体验

在快节奏的现代生活中&#xff0c;厨房已不仅仅是烹饪的场所&#xff0c;更是家庭温馨与创意的源泉。一款好的厨具&#xff0c;不仅能让烹饪变得轻松愉悦&#xff0c;更能为餐桌增添无限风味。今天&#xff0c;就让我们一起走进厨具的新世界&#xff0c;解锁那些令人爱不释手的…

手机长焦进化史:攀过十年,终抵云巅

今天&#xff0c;华为相机解决方案专家熊谌飞在《长焦十年之路对谈》直播中&#xff0c;首次系统揭秘了华为手机长焦技术的十年进化史。从P9双摄到Pura 80系列“一镜双目”&#xff0c;每一代影像旗舰&#xff0c;都有一段鲜为人知的诞生秘辛。不少观众这才恍然大悟&#xff1a…