為什么哈希表(字典)的查詢速度有時會突然變慢

哈希表(在許多語言中被稱為“字典”或“關聯數組”)的查詢速度,在理想情況下,應是接近“瞬時”的常數時間,然而,在特定場景下,其性能之所以會突然、無征兆地變慢,其根源,在于其底層的“數組+哈希函數”實現機制,在兩種關鍵情況下,會從高效的“直接尋址”模式,退化為低效的“遍歷查找”或“大規模數據遷移”模式。導致這種性能“斷崖”的五大核心原因涵蓋:發生了大量的“哈希沖突”、沖突鏈表或探測序列變得“過長”、觸發了“負載因子”閾值導致了“動態擴容”、底層數組正在進行“大規模”的數據遷移、以及選用了“質量不佳”的哈希函數

其中,觸發“負載因子”閾值導致的“動態擴容”,是導致查詢(更準確地說是“插入”操作時)“突然”變慢的最主要原因。這意味著,當哈希表內部的“擁擠”程度達到一個臨界點時,系統為了維持后續的查詢效率,會進行一次“重組”,這個重組過程,需要遍歷每一個已存在的元素,并為其重新計算存儲位置,對于一個已包含數百萬個元素的哈希表而言,這個過程,可能會造成一次肉眼可見的、秒級的“卡頓”。

一、速度的“魔法”:哈希表的“理想”狀態

在探討“為何會變慢”之前,我們必須首先深刻地理解,為何哈希表,在絕大多數情況下,都能夠實現那令人驚嘆的、近乎“瞬時”的查詢速度

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
英文地址,請注明出處:http://en.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…