Menu
CoddyTech

哈希映射(Hash Map)

最后更新

哈希映射存储键值对,让你平均以 O(1) 按键查到对应的值。它的工作方式和哈希表完全一样,只是每个桶中的条目同时存着键和它对应的值。存入或取出一个键值对时,先对键做哈希得到桶的下标,再在这个桶的链上查找匹配的键。点击上方的“播放”,观察键值对如何按哈希桶放置,以及如何按键取出一个值。

Python 的 dict、Java 的 HashMap 以及 JavaScript 的 Map 和对象背后都是这种结构。冲突的处理方式和哈希表相同(这里用的是拉链法),所以性能取决于好的哈希函数和较低的负载因子。

时间复杂度

操作平均最坏情况
Put(插入或更新)O(1)O(n)
Get(查找)O(1)O(n)
删除O(1)O(n)
空间O(n)O(n)

常用语言中的哈希映射

语言类型
Pythondict
JavaHashMap
JavaScriptMap / 对象
C++std::unordered_map
Gomap

示例演算

用 hash(key) % 8 把键值对 ("cat", 3)、("dog", 5)、("cat", 9)、("emu", 7) 插入一个有 8 个桶的映射。假设 hash("cat") % 8 = 2、hash("dog") % 8 = 5、hash("emu") % 8 = 2:

步骤结构操作
Put ("cat", 3)桶 2:[("cat", 3)]哈希到桶 2;链为空,所以追加这个键值对。
Put ("dog", 5)桶 2:[("cat", 3)],桶 5:[("dog", 5)]哈希到桶 5;链为空,所以追加这个键值对。
Put ("cat", 9)桶 2:[("cat", 9)],桶 5:[("dog", 5)]哈希到桶 2;键 "cat" 已在链中,所以把它的值更新为 9。
Put ("emu", 7)桶 2:[("cat", 9), ("emu", 7)],桶 5:[("dog", 5)]哈希到桶 2;与 "cat" 冲突,没有找到这个键,所以追加这个键值对。
Get "emu"桶 2:[("cat", 9), ("emu", 7)]哈希到桶 2;扫描链,跳过 "cat",匹配到 "emu",返回 7。

什么时候使用哈希映射

适合使用的情况不适合使用的情况
需要按精确的键进行平均 O(1) 的查找、插入和删除。需要让键保持有序或做范围查询,应使用平衡 BST 或有序映射。
键可哈希,并且相等的定义清晰(字符串、整数、元组)。键是可变的,插入后可能改变,这会让它落在错误的桶里。
要计数、去重、做缓存,或按 ID 建立索引。需要最坏情况 O(log n) 的保证,而不是均摊的平均上界。
不关心迭代顺序,或者按插入顺序保存的映射就够用。内存非常紧张,桶、负载因子的预留空间和指针都会增加开销。

Hash Map 代码

简洁、可运行的 Hash Map 实现,提供 Python, JavaScript, Java, C++, C 版本。选择一种语言,复制代码,或在 Coddy 在线编辑器中直接打开。

Hash Map 的 Python 代码

Python
1# Python's built-in dict is a hash map: O(1) average2# insert, lookup, and delete.3inventory = {"apple": 3, "banana": 7}4
5# Insert and update6inventory["cherry"] = 57inventory["apple"] += 28
9# Lookup, with .get for a safe default on missing keys10print("apple: ", inventory["apple"])11print("mango: ", inventory.get("mango", 0))12
13# Membership test and delete14print("banana in stock:", "banana" in inventory)15del inventory["banana"]16print("banana in stock:", "banana" in inventory)17
18# Iterate over key-value pairs19for fruit, count in sorted(inventory.items()):20    print(f"{fruit}: {count}")21
22# Classic hash map use case: counting frequencies23words = "the quick brown fox jumps over the lazy dog the end".split()24freq = {}25for word in words:26    freq[word] = freq.get(word, 0) + 127
28print("Occurrences of the:", freq["the"])29print("Most common word:  ", max(freq, key=freq.get))
在 Python 在线编辑器中运行这段代码

哈希映射常见问题

哈希映射和哈希表有什么区别?

它们本质上是同一种结构:一个用键的哈希值来定位的桶数组。在日常用法中,“哈希表”常指一组键或这种技术本身,而“哈希映射”强调存储的是键值对。有些语言还会在线程安全上加以区分(例如 Java 的 Hashtable 和 HashMap),但核心算法完全相同。

哈希映射的时间复杂度是多少?
在哈希函数良好、负载因子较低时,Put、Get 和删除的平均复杂度都是 O(1)。在所有键都冲突到同一个桶的极端情况下,它们会退化为 O(n),所以扩容和好的哈希函数很重要。
两个键哈希到同一个桶时,哈希映射怎么处理?

它会解决冲突。这个可视化使用拉链法:每个桶存一个小列表,新的键值对如果哈希到这里,就追加到这个列表中。查找时,映射会在这条短链上扫描匹配的键。另一种技术是开放寻址法,它会在数组中探测另一个空位。

哈希映射和二叉搜索树,该用哪个?
如果只需要按精确的键查找,并希望操作平均为 O(1),就用哈希映射。如果需要键有序、范围查询,或查找前驱和后继,就用平衡二叉搜索树(如 TreeMap 或 std::map),这些操作的代价是 O(log n)。BST 牺牲一点速度,换来哈希映射无法提供的顺序保证。
什么是负载因子?为什么它会触发扩容?
负载因子是已存条目数与桶数之比。它越高,链就越长,平均查找也越慢,所以大多数实现会在它超过某个阈值(如 0.75)时扩容,通常把桶数翻倍,并重新哈希所有条目。扩容是 O(n),但很少发生,均摊下来,平均操作仍保持 O(1)。
可以用列表这样的可变对象作为哈希映射的键吗?
通常不行。键必须可哈希,并且在映射中时哈希值要保持不变。例如,用 list 做键时 Python 会抛出 TypeError: unhashable type。如果在插入后修改了键,它的哈希值就会变化,映射再也无法在正确的桶里找到它,这个条目就悄无声息地丢失了。请使用字符串、数字或元组等不可变的键。
Coddy 编程语言插图

在 Coddy 掌握算法

开始