当前位置: 首页 > news >正文

【C++:unordered_set和unordered_map】 深度解析:使用、差异、性能与场景选择

🔥小叶-duck:个人主页

❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

一. 核心认知:unordered 系列容器是什么?

二. 模板参数介绍与基础接口使用

unordered_set:

1.1 模板参数介绍

1.2 核心接口使用

unordered_map:

2.1 模板参数介绍

2.2 核心接口使用

3、支持冗余的版本:unordered_multiset/unordered_multimap

三. 关键差异:unordered 系列 VS map/set

四、unordered_set和set的性能实测

结束语


前言

在 C++ 开发中,map/setunordered_map/unordered_set是高频使用的关联式容器,前者基于红黑树实现后者基于哈希表实现。刚接触两者时很多朋友都会纠结于 “该选哪一个”—— 其实核心在于理解两者的底层差异:红黑树保证有序但效率是 O (logN)哈希表追求 O (1) 极高平均效率但无序。本文聚焦unordered_map/unordered_set 的使用方法、与 map/set 的核心差异、性能对比,用代码示例和场景分析帮你快速掌握其用法,精准选择适合自己的容器。

一. 核心认知:unordered 系列容器是什么?

unordered_map 和 unordered_set 是 C++11 引入的关联式容器,底层基于哈希表(哈希桶)实现,核心特点如下:

  • 存储特性:unordered_set 存储单个 key(去重 + 无序),unordered_map 存储 key-value 对(key 去重 + 无序);
  • 效率:增删查改平均时间复杂度 O (1),最坏情况 O (N)(哈希冲突严重时);
  • 迭代器单向迭代器不支持 – 操作),遍历结果无序
  • 对 key 的要求:需支持 “转换为整形”(哈希函数需求)和 “相等比较”(冲突判断需求)。

参考文档:

unordered_set

unordered_multiset

unordered_map

unordered_multimap

二. 模板参数介绍与基础接口使用

unordered_set:

1.1 模板参数介绍

  • unordered_set 的声明如上,Key 就是 unordered_set 底层关键字的类型
  • unordered_set 默认要求 Key 支持转换为整形,如果不支持或者想按自己的需求走可以自行实现支持将 Key转成整形仿函数传给第二个模板参数

  • unordered_set 默认要求 Key 支持比较相等,如果不支持或者想按自己的需求走可以自行实现支持将Key比较相等仿函数传给第三个模板参数

  • unordered_set 底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第四个参数。

  • 一般情况下,我们都不需要传后三个模板参数

  • unordered_set 底层是用哈希桶实现,增删查平均效率是O(1),迭代器遍历不再有序,为了跟set 区分,所以取名 unordered_set。

1.2 核心接口使用

#include<iostream> #include <unordered_set> using namespace std; //unordered_set 核心接口使用: void test_unordered_set() { unordered_set<int> us; // 插入(返回pair<iterator, bool>,bool标记是否插入成功) us.insert(10); us.insert({ 30, 20, 40 });//和set一样支持初始化列表批量插入 //void insert ( initializer_list<value_type> il ); //迭代器遍历(无序) unordered_set<int>::iterator it = us.begin(); while (it != us.end()) { cout << *it << " "; it++; } cout << endl; // 查找(返回迭代器,未找到返回end()) auto find = us.find(20); if (find != us.end()) { cout << *find << "找到了!" << endl; } else { cout << "没有找到!" << endl; } // 删除(按key删除,返回删除个数) us.erase(30); for (auto e : us) { cout << e << " "; } cout << endl; // 其他常用接口 cout << "size:" << us.size() << endl; // 元素个数 cout << "empty:" << us.empty() << endl; // 是否为空 us.clear(); // 清空容器 cout << "clear后" << "size:" << us.size() << endl; } int main() { test_unordered_set(); return 0; }

unordered_map:

2.1 模板参数介绍

由于 unordered_map 的模板参数和 unordered_set 是完全一致的,区别点和 set与map之间的区别点是一样的,这里也就不过多赘述了。

2.2 核心接口使用

#include<iostream> #include <unordered_map> using namespace std; //unordered_map 核心接口使用: void test_unordered_map() { unordered_map<string, string> um; // 插入 um.insert({ "sort", "排序"}); um.insert(make_pair("left", "左边")); //迭代器遍历(无序) unordered_map<string, string>::iterator it = um.begin(); while (it != um.end()) { cout << it->first << ":" << it->second << endl; it++; } cout << endl; // []运算符(插入+访问/修改,最常用) um["right"] = "右边"; // 插入 um["left"] = "左边、留下"; // 修改 for (auto e : um) { cout << e.first << ":" << e.second << endl; } cout << endl; // 查找 auto find = um.find("sort"); if (find != um.end()) { cout << find->first << "找到了!" << endl; } else { cout << "没有找到!" << endl; } cout << endl; // 删除 um.erase("left"); for (auto e : um) { cout << e.first << ":" << e.second << endl; } } int main() { test_unordered_map(); return 0; }

3、支持冗余的版本:unordered_multiset/unordered_multimap

与 multiset/multimap 类似,支持 key 重复:

  • unordered_multiset:允许相同 key重复插入遍历无序
  • unordered_multimap:允许相同 key重复插入不支持 [] 运算符(key 不唯一);
  • 核心差异无去重机制,其他接口与 unordered_map/unordered_set 一致。

三. 关键差异:unordered 系列 VS map/set

对比维度unordered_map / unordered_setmap / set
底层结构哈希表(数组 + 链表/红黑树)红黑树(自平衡二叉搜索树)
元素顺序无序(取决于哈希函数)有序(按 key 默认升序排列)
时间复杂度平均O(1),最差O(n)稳定O(logN)
迭代器特性单向迭代器(仅支持向前遍历)双向迭代器(支持向前/向后遍历)
对 Key 的要求1. 支持==比较
2. 可计算哈希值
支持 < 比较(或自定义严格弱序)
内存占用较高(需预留桶空间减少冲突较低(树结构紧凑,无预留开销)
数据分布数据分散在桶中数据在树结构中平衡分布
主要优势极速查找(常数级平均时间)有序遍历、稳定性能
典型场景高频查询、缓存系统、去重操作需要有序数据、范围查询、顺序相关操作

四、unordered_set和set的性能实测

测试代码(核心逻辑):

#include<iostream> #include <unordered_set> #include <unordered_map> #include <set> using namespace std; //unordered_set和set的性能测试 void test() { const size_t N = 1000000; unordered_set<int> us; set<int> s; vector<int> v; v.reserve(N); srand(time(0)); for (size_t i = 0; i < N; ++i) { v.push_back(rand()); // N比较大时,重复值比较多 //v.push_back(rand() + i); // 重复值相对少 //v.push_back(i); // 没有重复,有序 } size_t begin1 = clock(); for (auto e : v) { s.insert(e); } size_t end1 = clock(); cout << "set insert:" << end1 - begin1 << "ms" << endl; size_t begin2 = clock(); us.reserve(N); for (auto e : v) { us.insert(e); } size_t end2 = clock(); cout << "unordered_set insert:" << end2 - begin2 << "ms" << endl << endl; int m1 = 0; size_t begin3 = clock(); for (auto e : v) { auto ret = s.find(e); if (ret != s.end()) { ++m1; } } size_t end3 = clock(); cout << "set find:" << end3 - begin3 << "->" << m1 << endl; int m2 = 0; size_t begin4 = clock(); for (auto e : v) { auto ret = us.find(e); if (ret != us.end()) { ++m2; } } size_t end4 = clock(); cout << "unorered_set find:" << end4 - begin4 << "->" << m2 << endl; cout << "插入数据个数:" << s.size() << endl; cout << "插入数据个数:" << us.size() << endl << endl; size_t begin5 = clock(); for (auto e : v) { s.erase(e); } size_t end5 = clock(); cout << "set erase:" << end5 - begin5 << "ms" << endl; size_t begin6 = clock(); for (auto e : v) { us.erase(e); } size_t end6 = clock(); cout << "unordered_set erase:" << end6 - begin6 << "ms" << endl << endl; } int main() { test(); return 0; }

三组测试结果

(1)N比较大且重复值比较多:

(2)N比较大且重复值相对少:

(3)N比较大且没有重复,有序:

关键结论:

  • unordered 系列在插入、查找、删除场景下均显著快于 map/set,尤其是高频查询场景;
  • unordered 系列使用时建议先用reserve(N)预分配空间,避免频繁扩容导致性能下降;
  • 数据量越小性能差距越不明显数据量越大,unordered 系列的优势越突出数据有序时,unoredered系列的插入效率没 set 高

结束语

到此,unordered_set和unordered_map的使用介绍就讲解完了。
unordered_map/unordered_set 的核心价值是 “以空间换时间”,用哈希表的结构实现 O (1) 平均效率,完美适配高频查询场景;而 map/set 则以 “有序 + 稳定” 为核心优势,适合需要排序或空间敏感的场景。两者的接口高度兼容,切换成本极低,实际使用开发中可根据 “是否需要有序” 和 “查询频率” 快速决策。希望对大家学习C++能有所收获!

C++参考文档:
https://legacy.cplusplus.com/reference/
https://zh.cppreference.com/w/cpp
https://en.cppreference.com/w/

http://www.jsqmd.com/news/562879/

相关文章:

  • C++通讯录管理系统开发(数组+函数+指针+结构体)
  • 深入解析MyBatisPlus批量插入性能瓶颈与优化实战
  • 深度拆解MySQL InnoDB存储引擎架构:从内存到磁盘的全链路解析
  • FLUX.1-dev像素艺术生成器效果:超分辨率重建前后对比分析
  • 分布式存储的监控与告警:从理论到实践
  • 2023年HCA-Security综合安防考试
  • 项目实训第一次讨论
  • 2026年护栏网应用白皮书畜牧养殖领域解析 - 优质品牌商家
  • Cojson:面向MCU的零分配JSON解析器
  • 220v转24v 32v电流300W电源方案
  • 宝塔面板异地备份数据全攻略:从本地到云端的安全守护
  • UWB(AOA)技术是如何应用在智慧工厂的
  • 7B小模型吊打GPT-5?CarePilot用Actor-Critic范式攻克医疗软件自动化
  • 万用自动连点点击器 v2.2.4解锁版-无需ROOT自动连点
  • CHORD-X效果实测:对比不同参数下生成报告的连贯性与深度
  • XLR8RC库:嵌入式RC信号高精度脉宽捕获方案
  • 金蝶k3软件常用基础SQL数据表
  • 在Ubuntu 20.04上搞定创龙T113 SDK编译:我踩过的那些Python和gdbus的坑
  • FastbootEnhance:Windows上最直观的Fastboot工具箱与Payload提取器
  • 2026镇海区空调及进口热水器维修行业白皮书 - 优质品牌商家
  • 告别PXE!用iPXE在CentOS 8.5上搭建一个能同时装Win11和Linux的万能网络启动盘
  • 2026年保温卷帘门公司权威推荐:成都卷帘门/电动保温卷帘门/电动卷帘门/车库保温卷帘门/车库卷帘门/选择指南 - 优质品牌商家
  • 告别串口接收烦恼!手把手配置华大HC32F460的UART超时中断(附RT-Thread驱动示例)
  • 【Frida Android】实战篇:Frida-Trace 进阶追踪——JNI 函数调用栈与参数解析
  • 崩溃体验馆:付费观赏系统死机的艺术
  • 如何通过FastbootEnhance实现Android设备快速刷机与分区管理
  • 概率预测实战 —— DeepAR 模型在电力负荷预测中的应用
  • 别再傻傻用相机了!用海康VisionMaster本地图片也能跑算法,附完整配置流程
  • 稳定的第三方软件库
  • GitHub Desktop 中文界面突破方案:告别语言障碍的效率革命