时间:2025-04-27 13:10
人气:
作者:admin
大家好!今天咱们聊一个看似简单却经常被忽视的话题:C++中的map和unordered_map到底有啥区别?
选错了容器,你的程序可能就慢了 10 倍不止!这可不是危言耸听,而是实打实的性能差距。
前几天我同事小王一脸沮丧地走过来:"我的程序怎么这么慢啊,数据量一大就卡得不行..."
我瞄了一眼他的代码,发现他在处理几十万条数据时用的是map,而不是unordered_map。简单改了一下容器类型后,程序速度立马提升了 8 倍多!
小王震惊了:"啥?就改个容器名字,速度差这么多?"
是的,就是这么神奇!今天我就带大家彻底搞清楚这两个容器的区别,以后再也不踩这个坑。
微信搜索 「跟着小康学编程」,关注我,后续还有更多硬核技术文章分享,带你玩转 Linux C/C++ 编程!????
map就像一个有序的字典,它会自动把你放进去的键值对按键排序。
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> scoreMap;
scoreMap["Zhang"] = 85;
scoreMap["Li"] = 92;
scoreMap["Wang"] = 78;
// 遍历时会按键的字母顺序输出
for (const auto& student : scoreMap) {
std::cout << student.first << ": " << student.second << std::endl;
}
return 0;
}
// 输出结果:
// Li: 92
// Wang: 78
// Zhang: 85
// (按照字母顺序)
unordered_map则像个不讲究顺序的字典,它只关心能不能快速找到东西,至于排序?不存在的!
#include <iostream>
#include <unordered_map>
int main() {
std::unordered_map<std::string, int> scoreMap;
scoreMap["Zhang"] = 85;
scoreMap["Li"] = 92;
scoreMap["Wang"] = 78;
// 遍历时输出顺序不确定
for (const auto& student : scoreMap) {
std::cout << student.first << ": " << student.second << std::endl;
}
return 0;
}
// 可能的输出结果:
// Wang: 78
// Zhang: 85
// Li: 92
// (顺序可能每次运行都不同)
map内部是用 红黑树 实现的。红黑树是一种自平衡的二叉查找树。
想象一下,如果把map比作一个图书馆:
// map的简化结构示意图(红黑树)
D
/ \
B F
/ \ / \
A C E G
在上面的图中,每个字母代表一个键。查找键"E"的过程:
unordered_map内部是用哈希表实现的。
继续用图书馆打比方:
// unordered_map的简化结构示意图(哈希表)
桶0: [C] -> [K]
桶1: [A]
桶2:
桶3: [D] -> [H]
桶4: [B]
桶5: [G]
桶6: [F] -> [J]
桶7: [E] -> [I]
在上图中,每个字母代表一个键。查找键"H"的过程:
让我们做个全面的性能对比,分别测试插入、查找、删除和修改这四种操作:
#include <iostream>
#include <map>
#include <unordered_map>
#include <chrono>
#include <string>
#include <vector>
#include <random>
// 计时辅助函数
template<typename Func>
long long timeOperation(Func func) {
auto start = std::chrono::high_resolution_clock::now();
func();
auto end = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
}
int main() {
const int COUNT = 100000;
// 准备随机数据
std::vector<int> keys;
for (int i = 0; i < COUNT; i++) {
keys.push_back(i);
}
// 打乱顺序用于随机访问
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(keys.begin(), keys.end(), g);
std::map<int, int> orderedMap;
std::unordered_map<int, int> unorderedMap;
// 1. 插入性能
auto mapInsertTime = timeOperation([&]() {
for (int i = 0; i < COUNT; i++) {
orderedMap[i] = i * 2;
}
});
auto unorderedMapInsertTime = timeOperation([&]() {
for (int i = 0; i < COUNT; i++) {
unorderedMap[i] = i * 2;
}
});
// 2. 查找性能(顺序访问)
auto mapLookupTime = timeOperation([&]() {
int result = 0;
for (int i = 0; i < COUNT; i++) {
result += orderedMap[i];
}
// 防止编译器优化掉
volatile int dummy = result;
});
auto unorderedMapLookupTime = timeOperation([&]() {
int result = 0;
for (int i = 0; i < COUNT; i++) {
result += unorderedMap[i];
}
// 防止编译器优化掉
volatile int dummy = result;
});
// 3. 查找性能(随机访问)
auto mapRandomLookupTime = timeOperation([&]() {
int result = 0;
for (int key : keys) {
result += orderedMap[key];
}
// 防止编译器优化掉
volatile int dummy = result;
});
auto unorderedMapRandomLookupTime = timeOperation([&]() {
int result = 0;
for (int key : keys) {
result += unorderedMap[key];
}
// 防止编译器优化掉
volatile int dummy = result;
});
// 4. 修改性能
auto mapUpdateTime = timeOperation([&]() {
for (int i = 0; i < COUNT; i++) {
orderedMap[i] = i * 3;
}
});
auto unorderedMapUpdateTime = timeOperation([&]() {
for (int i = 0; i < COUNT; i++) {
unorderedMap[i] = i * 3;
}
});
// 5. 删除性能
auto mapEraseTime = timeOperation([&]() {
for (int key : keys) {
if (key % 2 == 0) { // 删除一半的元素
orderedMap.erase(key);
}
}
});
auto unorderedMapEraseTime = timeOperation([&]() {
for (int key : keys) {
if (key % 2 == 0) { // 删除一半的元素
unorderedMap.erase(key);
}
}
});
// 打印结果
std::cout << "操作\t\tmap(微秒)\tunordered_map(微秒)\t性能比" << std::endl;
std::cout << "插入\t\t" << mapInsertTime << "\t\t" << unorderedMapInsertTime
<< "\t\t\t" << (float)mapInsertTime / unorderedMapInsertTime << std::endl;
std::cout << "顺序查找\t" << mapLookupTime << "\t\t" << unorderedMapLookupTime
<< "\t\t\t" << (float)mapLookupTime / unorderedMapLookupTime << std::endl;
std::cout << "随机查找\t" << mapRandomLookupTime << "\t\t" << unorderedMapRandomLookupTime
<< "\t\t\t" << (float)mapRandomLookupTime / unorderedMapRandomLookupTime << std::endl;
std::cout << "修改\t\t" << mapUpdateTime << "\t\t" << unorderedMapUpdateTime
<< "\t\t\t" << (float)mapUpdateTime / unorderedMapUpdateTime << std::endl;
std::cout << "删除\t\t" << mapEraseTime << "\t\t" << unorderedMapEraseTime
<< "\t\t\t" << (float)mapEraseTime / unorderedMapEraseTime << std::endl;
return 0;
}
// 输出结果:
// 操作 map(微秒) unordered_map(微秒) 性能比
// 插入 225419 116690 1.93178
// 顺序查找 103715 20122 5.15431
// 随机查找 127432 25890 4.92205
// 修改 104918 20597 5.09385
// 删除 130040 29996 4.33524
从测试结果可以清晰地看出:
unordered_map比map快约1.9倍。这是因为map每次插入都需要维护红黑树的平衡,而unordered_map只需计算哈希值并放入对应的桶。unordered_map比map快约5.2倍unordered_map比map快约4.9倍查找是unordered_map最显著的优势,无论是顺序还是随机访问模式下都有明显提升。
unordered_map比map快约5.1倍。修改操作本质上是先查找再赋值,所以性能差距与查找操作接近。unordered_map比map快约4.3倍。map删除元素后可能需要重新平衡树,而unordered_map只需从哈希表中删除节点。综合来看,unordered_map在所有操作上都显著优于map,特别是在查找和修改操作上,性能提升达到了5倍左右。这意味着在大多数不需要有序遍历的场景下,unordered_map是更优的选择。
记住这些差异,在实际开发中选择合适的容器,可以为你的程序带来显著的性能提升。
1、 需要有序遍历:如果你需要按键的顺序遍历元素
// 想按学生姓名字母顺序打印成绩单
std::map<std::string, int> scoreCard;
// ... 添加数据 ...
for (const auto& item : scoreCard) {
std::cout << item.first << ": " << item.second << std::endl;
}
2、 需要范围查询:找出所有键在某个范围内的元素
// 查找所有名字在A到C之间的学生
auto start = scoreCard.lower_bound("A");
auto end = scoreCard.upper_bound("C");
for (auto it = start; it != end; ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
3、 需要稳定的性能:最坏情况下查找复杂度是确定的O(log n)
1、 只关心查找速度:大多数情况下只是用来查找,不关心顺序
// 快速查找某个学生的成绩
std::unordered_map<std::string, int> scoreDB;
// ... 添加数据 ...
std::cout << "Zhang's score: " << scoreDB["Zhang"] << std::endl;
2、 数据量大:对于大数据量,unordered_map 的常数时间复杂度 O(1) 明显优于 map 的 O(log n)
3、 不需要排序:如果不需要按键排序,就没必要付出排序的成本
对于unordered_map,如果用自定义类型作为键,必须提供哈希函数和相等比较函数:
struct Student {
std::string name;
int id;
bool operator==(const Student& other) const {
return id == other.id;
}
};
// 为Student提供哈希函数
namespace std {
template<>
struct hash<Student> {
size_t operator()(const Student& s) const {
return hash<int>()(s.id);
}
};
}
// 现在可以使用了
std::unordered_map<Student, int> studentScores;
unordered_map在某些情况下可能会遇到哈希冲突,导致性能下降。如果你的应用对性能稳定性要求高,可能需要考虑使用map。
unordered_map通常比map消耗更多内存,因为哈希表为了降低冲突概率,会预留一定的空间。
unordered_mapmap;如果只需要快速查找,选择unordered_map选对容器,事半功倍;选错容器,徒增烦恼。
记住这些区别,下次写代码时,就能轻松选择正确的容器,让你的程序飞起来!
你遇到过因为选错容器导致的性能问题吗?欢迎在评论区分享你的"血泪"经历~
如果你也对性能优化着迷,或想让你的代码更加高效,不妨关注我的公众号「跟着小康学编程」。
在这里,我用简单的比喻解释复杂概念,用有趣的案例展示技术原理。无论是 STL 容器选择,还是内存优化策略,都能找到通俗易懂的解答。
每周更新,不见不散! ????
如果你觉得这篇文章对你有帮助,别忘了点赞、收藏、关注哦~ 你的支持是我持续创作的动力!
点击下方公众号名片即可关注。

哦对了,我还建了个技术交流群,大家一起聊技术、解答问题。卡壳了?不懂的地方?随时在群里提问!不只是我,群里还有一堆技术大佬随时准备帮你解惑。一起学,才有动力嘛!
