C++标准库中std::map和std::unordered_map对比及如何选择

0. 概述

std::map和std::unordered_map都是一种存储{key, value}的容器,并提供成员函数来高效地插入、搜索和删除键值对。 顾名思义,std::map是有序的,std::unordered_map是无序的。 后者以前叫做hash_map。

以下是他们的不同点:

容器mapunordered_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

  1. 关键字类型的hash函数设计的很差, 或者==运算符的性能极差, 导致hash过程太耗时;
  2. 对内存使用有严格要求, 不能接受存储hash table的额外内存开销;
  3. 元素要求按顺序存储, 或者常常需要关联访问一个元素的上一个/下一个元素, 或者需要遍历整个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

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值