C++标准库中std::map和std::unordered_map对比及如何选择
std::map和std::unordered_map都是一种存储{key, value}的容器,并提供成员函数来高效地插入、搜索和删除键值对。 顾名思义,std::map是有序的,std::unordered_map是无序的。 后者以前叫做hash_map。
0. 概述
std::map和std::unordered_map都是一种存储{key, value}的容器,并提供成员函数来高效地插入、搜索和删除键值对。 顾名思义,std::map是有序的,std::unordered_map是无序的。 后者以前叫做hash_map。
以下是他们的不同点:
容器 | map | unordered_map |
---|---|---|
有序性 | 有序 | 无序 |
内部实现方式 | 平衡二叉查找树 | 哈希表 |
查找 时间复杂度 | O(logN) | 平均O(1), 最坏O(N) |
插入、删除 时间复杂度 | O(longN)+平衡时间 | 平均O(1), 最坏O(N) |
1. 内部实现
std::map内部数据存储方式为平衡二叉查找树(banlanced BST, 如红黑树), 因此数据是按key有序存储的(注:二叉查找树的中序遍历是有序的)。
std::unordered_map内部数据存储方式为哈希表, 数据是无序的。(注:数据也不一定是连续内存, 因为hash表中记录数据存储地址即可。
2. 空间复杂度
因为需要额外的空间存储哈希表,所以unordered_map比map使用更多的内存。
3. 时间复杂度 :
map的性能和平衡二叉查找树相同, unordered_map的性能和哈希表相关。
以下为使用Google的“sparsehash project”,进行的性能测试结果:
$ ./time_hash_map
TR1 UNORDERED_MAP (4 byte objects, 10000000 iterations):
map_grow 126.1 ns (27427396 hashes, 40000000 copies) 290.9 MB
map_predict/grow 67.4 ns (10000000 hashes, 40000000 copies) 232.8 MB
map_replace 22.3 ns (37427396 hashes, 40000000 copies)
map_fetch 16.3 ns (37427396 hashes, 40000000 copies)
map_fetch_empty 9.8 ns (10000000 hashes, 0 copies)
map_remove 49.1 ns (37427396 hashes, 40000000 copies)
map_toggle 86.1 ns (20000000 hashes, 40000000 copies)
STANDARD MAP (4 byte objects, 10000000 iterations):
map_grow 225.3 ns ( 0 hashes, 20000000 copies) 462.4 MB
map_predict/grow 225.1 ns ( 0 hashes, 20000000 copies) 462.6 MB
map_replace 151.2 ns ( 0 hashes, 20000000 copies)
map_fetch 156.0 ns ( 0 hashes, 20000000 copies)
map_fetch_empty 1.4 ns ( 0 hashes, 0 copies)
map_remove 141.0 ns ( 0 hashes, 20000000 copies)
map_toggle 67.3 ns ( 0 hashes, 20000000 copies)
4. 关键字类型key_type的要求
因为map是按二叉查找树存储的,需要比较key的大小,所以关键字类型必须定义元素比较的方法。标准库使用关键字类型的**<运算符来**比较, 也可以自定义比较操作(对于没有<运算符的类型,比如自定义类,则必须自定义比较操作), 自定义的比较操作必须是严格弱序的。
如,std::map<Point, double, PointCmp> mag;
类似的,unordered_map的关键字类型需要有hash函数来计算hash值。因为可能存在hash冲突,即不同元素的hash相同,因此还需要关键字类型有**==运算符**来比较元素。 标准库为内置类型提供了hash模板, 如int型会被hash到它本身, std::hash<const char*>产生内存地址值的hash值。 hash函数和
==
运算符都是可以定义的,关键字类型没有的时候必须自定义。
如,std::unordered_map<Key, std::string, KeyHash, KeyEqual> m6;
5. 选用哪一个,看完不再纠结
两者的接口差不多,基本可以互换。
一般来说unordered_map的综合性能比map要好,因此,通常我们可以使用unorderd_map代替map。
以下情况推荐使用map:
- 关键字类型的hash函数设计的很差, 或者==运算符的性能极差, 导致hash过程太耗时;
- 对内存使用有严格要求, 不能接受存储hash table的额外内存开销;
- 元素要求按顺序存储, 或者常常需要关联访问一个元素的上一个/下一个元素, 或者需要遍历整个map。
其他情况推荐使用unordered_map。
Ref:
https://www.geeksforgeeks.org/map-vs-unordered_map-c/
https://thispointer.com/map-vs-unordered_map-when-to-choose-one-over-another/
https://stackoverflow.com/questions/2196995/is-there-any-advantage-of-using-map-over-unordered-map-in-case-of-trivial-keys
https://en.cppreference.com/w/cpp/container/unordered_map
https://en.cppreference.com/w/cpp/container/map
https://en.cppreference.com/w/cpp/utility/hash
更多推荐
所有评论(0)