map/unordered_map原理和使用整理

1.结论

新版的hash_map都是unordered_map了,这里只说unordered_map和map.

运行效率方面:unordered_map最高,而map效率较低但 提供了稳定效率和有序的序列。

占用内存方面:map内存占用略低,unordered_map内存占用略高,而且是线性成比例的。

需要无序容器,快速查找删除,不担心略高的内存时用unordered_map;有序容器稳定查找删除效率,内存很在意时候用map

2.原理

map的内部实现是二叉平衡树(红黑树);hash_map内部是一个hash_table一般是由一个大vector,vector元素节点可挂接链表来解决冲突,来实现.

hash_map 其插入过程是:
  1. 得到key
  2. 通过hash函数得到hash值
  3. 得到桶号(一般都为hash值对桶数求模)
  4. 存放key和value在桶内。

其取值过程是:
  1. 得到key
  2. 通过hash函数得到hash值
  3. 得到桶号(一般都为hash值对桶数求模)
  4. 比较桶的内部元素是否与key相等,若都不相等,则没有找到。
  5. 取出相等的记录的value。

hash_map中直接地址用hash函数生成,解决冲突,用比较函数解决。

3.内存占用测试

测试代码:
测试条件window下,VS2015 C++。string为key, int 为value。
1.UnorderMap:
[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <unordered_map>    
  2. #include <string>    
  3. #include <iostream>  
  4. #include <windows.h>  
  5. #include <psapi.h>    
  6. #pragma comment(lib,"psapi.lib")   
  7. using namespace std;  
  8. using namespace stdext;  
  9. void showMemoryInfo(void)  
  10. {  
  11.     HANDLE handle = GetCurrentProcess();  
  12.     PROCESS_MEMORY_COUNTERS pmc;  
  13.     GetProcessMemoryInfo(handle, &pmc, sizeof(pmc));  
  14.     cout << "Memory Use:" << pmc.WorkingSetSize/1024.0f << "KB/" << pmc.PeakWorkingSetSize/1024.0f << "KB, Virtual Memory Use:" << pmc.PagefileUsage/1024.0f << "KB/" << pmc.PeakPagefileUsage/1024.0f << "KB" << endl;  
  15. }  
  16.   
  17. //define the class    
  18. /*-------------------------------------------*/  
  19. /*函数类 
  20. *作为hash_map的hash函数 
  21. *string没有默认的hash函数 
  22. */  
  23. class str_hash {  
  24. public:  
  25.     size_t operator()(const string& str) const  
  26.     {  
  27.         unsigned long __h = 0;  
  28.         for (size_t i = 0; i < str.size(); i++)  
  29.             __h = 5 * __h + str[i];  
  30.         return size_t(__h);  
  31.     }  
  32. };  
  33.   
  34. /*-------------------------------------------*/  
  35. /*函数类 
  36. *作为hash_map的比较函数 ) 
  37. *(查找的时候不同的key往往可能对用到相同的hash值 
  38. */  
  39. class str_compare  
  40. {  
  41. public:  
  42.     bool operator()(const string& str1, const string& str2)const  
  43.     {  
  44.         return   str1 == str2;  
  45.     }  
  46. };  
  47.   
  48. struct CharLess : public binary_function<const string&, const string&, bool>  
  49. {  
  50. public:  
  51.     result_type operator()(const first_argument_type& _Left, const second_argument_type& _Right) const  
  52.     {  
  53.         return(_Left.compare(_Right) < 0 ? true : false);  
  54.     }  
  55. };  
  56.   
  57. int main()  
  58. {  
  59.       
  60.     cout << "Test HashMap(unorder map) Memory Use  Start..."<< endl;  
  61.     // VC下自定义类型    
  62.     unordered_map<string, int, hash_compare<string, CharLess> > CharHash;  
  63.     for (int i = 0; i < 10000000; i++)  
  64.     {  
  65.         string key = to_string(i);  
  66.         CharHash[key] = i;  
  67.     }  
  68.     cout << "Test HashMap(unorder map) Memory Use  End." << endl;  
  69.     showMemoryInfo();  
  70.     while (1);  
  71.     return 0;  
  72. }  


2.map:
[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <iostream>  
  2. #include <map>  
  3. #include <string>  
  4. #include <windows.h>  
  5. #include <psapi.h>    
  6. #pragma comment(lib,"psapi.lib")   
  7. using namespace std;  
  8. void showMemoryInfo(void)  
  9. {  
  10.     HANDLE handle = GetCurrentProcess();  
  11.     PROCESS_MEMORY_COUNTERS pmc;  
  12.     GetProcessMemoryInfo(handle, &pmc, sizeof(pmc));  
  13.     cout << "Memory Use:" << pmc.WorkingSetSize / 1024.0f << "KB/" << pmc.PeakWorkingSetSize / 1024.0f << "KB, Virtual Memory Use:" << pmc.PagefileUsage / 1024.0f << "KB/" << pmc.PeakPagefileUsage / 1024.0f << "KB" << endl;  
  14. }  
  15.   
  16.   
  17. int main()  
  18. {  
  19.     cout << "Test Map(Red-Black Tree) Memory Use  Start..." << endl;  
  20.     // VC下自定义类型    
  21.     //map<const char*, int, hash_compare<const char*, CharLess> > CharHash;  
  22.     map<string, int> CharMap;  
  23.     for (int i = 0; i < 10000000; i++)  
  24.     {  
  25.         string key = to_string(i);  
  26.         CharMap[key] = i;  
  27.     }  
  28.     cout << "Test Map(Red-Black Tree) Memory Use  End." << endl;  
  29.     showMemoryInfo();  
  30.     while (1);  
  31.     return 0;  
  32. }  



测试结果:
1000个元素:
map:

unorder_map:

10万个元素:
map:

unorder_map:

1000万个元素:
map:

unorder_map:

可以看到unordermap始终比map内存空间占用量大些,而且是线性成比例的。

4.性能特点

非频繁的查询用map比较稳定;频繁的查询用hash_map效率会高一些,c++11中的unordered_map查询效率会更高一些但是内存占用比hash_map稍微大点。unordered_map 就是 boost 里面的 hash_map 实现。

其实,stl::map对于与java中的TreeMap,而boost::unordered_map对应于java中的HashMap。
python中的map就是hashmap实现的,所以查询效率会比C++的map查询快。(java,python官方版的虚拟机都是用C语言实现的,所以内部的思想和方法都是通用的。)

若考虑有序,查询速度稳定,容器元素量少于1000,非频繁查询那么考虑使用map。
若非常高频查询(100个元素以上,unordered_map都会比map快),内部元素可非有序,数据大超过1k甚至几十万上百万时候就要考虑使用unordered_map
(元素上千万上亿时4GB的内存就要担心内存不足了,需要数据库存储过程挪动到磁盘中)。
hash_map相比unordered_map就是千万级别以上内存占用少15MB,上亿时候内存占用少300MB,百万以下都是unordered_map占用内存少,
且unordered_map插入删除相比hash_map都快一倍,查找效率相比hash_map差不多,或者只快了一点约1/50到1/100。
综合非有序或者要求稳定用map,都应该使用unordered_map,set类型也是类似的。
unordered_map 查找效率快五倍,插入更快,节省一定内存。如果没有必要排序的话,尽量使用 hash_map(unordered_map 就是 boost 里面的 hash_map 实现)

5.使用unordered_map

unordered_map需要重载hash_value函数,并重载operator ==运算符。
详细参考见(感谢orzlzro写的这么好的文章):

6.使用Hash_map需要注意的问题

/* *
 *\author peakflys
 *\brief 演示hash_map键值更改造成的问题
 
*/
#include <iostream>
#include <ext/hash_map>
struct Unit
{
     char name[32];
    unsigned  int score;
    Unit( const  char *_name, const unsigned  int _score) : score(_score)
    {   
        strncpy(name,_name,32);
    }   
};
int main()
{
    typedef __gnu_cxx::hash_map< char*,Unit*> uHMap;
    typedef uHMap::value_type hmType;
    typedef uHMap::iterator hmIter;
    uHMap hMap;
    Unit *unit1 =  new Unit("peak",100);
    Unit *unit2 =  new Unit("Joey",20);
    Unit *unit3 =  new Unit("Rachel",40);
    Unit *unit4 =  new Unit("Monica",90);
    hMap[unit1->name] = unit1;
    hMap[unit2->name] = unit2;
    hMap.insert(hmType(unit3->name,unit3));
    hMap.insert(hmType(unit4->name,unit4));
     for(hmIter it=hMap.begin();it!=hMap.end();++it)
    {   
        std::cout<<it->first<<"\t"<<it->second->score<<std::endl; // 正常操作
    }   
     for(hmIter it=hMap.begin();it!=hMap.end();++it)
   {
        Unit *unit = it->second;
        //hMap.erase(it++);
        delete unit;  // delete释放节点内存,但是hMap没有除去,造成hMap内部错乱,有可能宕机
    } 
     hmIter it = hMap.begin();
    strncpy(it->first,"cc",32); // 强行更改
     for(hmIter it=hMap.begin();it!=hMap.end();++it)
    {   
        std::cout<<it->first<<"\t"<<it->second->score<<std::endl; // 死循环,原因参加上面++操作说明
/*operator++ 操作是从_M_cur开始,优先_M_cur->_M_next,为空时遍历vector直至找到一个_M_cur不为空的节点,遍历vector 时需要取它对应的桶位置(参砍上面hash_map取值过程),_M_bkt_num_key(key)中key的值是修改后的值,假如你改的键值,通过 此函数得到的桶位置在你当前元素之前,这样就造成了死循环.
*/
    }   
     return 0;

7.VC下参考实例

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include "stdafx.h"  
  2.   
  3. // 存放过程:key->hash函数->hash值对桶数求模得到桶号(桶有值则解决冲突),存放key和value在桶内  
  4. // 取回过程:key->hash函数->hash值对桶数求模得到桶号(桶有值则解决冲突),比较桶内的key是否相等,  
  5. // 若不相等则返回空迭代器,否则返回迭代器。  
  6.   
  7. // 1.hash_map为下面类型的key定义了hash寻址函数(用于从key到hash值)和哈希比较函数(用于解决冲突)。  
  8. //struct hash<char*>  
  9. //struct hash<const char*>  
  10. //struct hash<char>   
  11. //struct hash<unsigned char>   
  12. //struct hash<signed char>  
  13. //struct hash<short>  
  14. //struct hash<unsigned short>   
  15. //struct hash<int>   
  16. //struct hash<unsigned int>  
  17. //struct hash<long>   
  18. //struct hash<unsigned long>   
  19. // 内建的类型直接 hash_map<int, string> mymap;像普通map一样使用即可。  
  20.   
  21. // 2.自定义hash函数和比较函数  
  22. //在声明自己的哈希函数时要注意以下几点:  
  23.   
  24. //使用struct,然后重载operator().  
  25. //返回是size_t  
  26. //参数是你要hash的key的类型。  
  27. //函数是const类型的。   
  28.   
  29. // 定义自己的比较函数:  
  30. //使用struct,然后重载operator().  
  31. //返回是bool  
  32. //参数是你要hash的key的类型的两个常量参数,用于比较。  
  33. //函数是const类型的。  
  34.   
  35. // 自定义hash函数和比较函数的使用:  
  36. // hash_map<ClassA, string, hash_A, equal_A> hmap;  
  37.   
  38. // 3.hash_map使用的常用函数  
  39.   
  40. //hash_map的函数和map的函数差不多。具体函数的参数和解释,请参看:STL 编程手册:Hash_map,这里主要介绍几个常用函数。  
  41. //  
  42. //hash_map(size_type n) 如果讲究效率,这个参数是必须要设置的。n 主要用来设置hash_map 容器中hash桶的个数。  
  43. //桶个数越多,hash函数发生冲突的概率就越小,重新申请内存的概率就越小。n越大,效率越高,但是内存消耗也越大。  
  44. //  
  45. //const_iterator find(const key_type& k) const. 用查找,输入为键值,返回为迭代器。  
  46. //  
  47. //data_type& operator[](const key_type& k) . 这是我最常用的一个函数。因为其特别方便,可像使用数组一样使用。  
  48. //不过需要注意的是,当你使用[key ]操作符时,如果容器中没有key元素,这就相当于自动增加了一个key元素。  
  49. //因此当你只是想知道容器中是否有key元素时,你可以使用find。如果你希望插入该元素时,你可以直接使用[]操作符。  
  50. //  
  51. //insert 函数。在容器中不包含key值时,insert函数和[]操作符的功能差不多。但是当容器中元素越来越多,  
  52. //每个桶中的元素会增加,为了保证效率,hash_map会自动申请更大的内存,以生成更多的桶。因此在insert以后,  
  53. //以前的iterator有可能是不可用的。  
  54. //  
  55. //erase 函数。在insert的过程中,当每个桶的元素太多时,hash_map可能会自动扩充容器的内存。  
  56. //但在sgi stl中是erase并不自动回收内存。因此你调用erase后,其他元素的iterator还是可用的。   
  57.   
  58. #include <hash_map>  
  59. #include <string>  
  60. #include <iostream>  
  61. using namespace std;  
  62. using namespace stdext;   
  63. //define the class  
  64. /*-------------------------------------------*/    
  65. /*函数类  
  66.  *作为hash_map的hash函数   
  67.  *string没有默认的hash函数   
  68.  */     
  69. class str_hash{    
  70.       public:    
  71.        size_t operator()(const string& str) const    
  72.         {    
  73.                 unsigned long __h = 0;    
  74.                 for (size_t i = 0 ; i < str.size() ; i ++)    
  75.                 __h = 5*__h + str[i];    
  76.                 return size_t(__h);    
  77.         }    
  78. };  
  79.   
  80. /*-------------------------------------------*/    
  81. /*函数类   
  82.  *作为hash_map的比较函数 )  
  83.  *(查找的时候不同的key往往可能对用到相同的hash值  
  84. */     
  85. class str_compare    
  86. {    
  87.       public:    
  88.              bool operator()(const string& str1,const string& str2)const    
  89.              {return   str1==str2;}    
  90. };    
  91.   
  92. struct CharLess : public binary_function<const char*, const char*, bool>  
  93. {  
  94. public:  
  95.     result_type operator()(const first_argument_type& _Left, const second_argument_type& _Right) const  
  96.     {  
  97.         return(strcmp(_Left, _Right) < 0 ? true : false);  
  98.     }  
  99. };    
  100.   
  101. int main()  
  102. {  
  103.     // 内建类型  
  104.     hash_map<int,string> myHashMap;  
  105.     myHashMap[0] = "JesseCen";  
  106.     myHashMap[1] = "OZZ";  
  107.     hash_map<int,string>::iterator itrHash = myHashMap.find(0);  
  108.     if(itrHash != myHashMap.end())  
  109.     {  
  110.         cout<<"My Name is:"<<itrHash->second.c_str()<<endl;  
  111.     }  
  112.   
  113.     // VC下自定义类型  
  114.     hash_map<const char*, int, hash_compare<const char*, CharLess> > CharHash;  
  115.     CharHash["a"] = 123;  
  116.     CharHash["b"] = 456;   
  117.     hash_map<const char*, int, hash_compare<const char*, CharLess> >::iterator itrChar = CharHash.find("b");  
  118.     if( itrChar != CharHash.end())  
  119.     {  
  120.         cout<<"The find number is:"<< itrChar->second<<endl;  
  121.     }  
  122.   
  123.     return 0;  
  124. }  

转自:
http://blog.youkuaiyun.com/blues1021/article/details/45054159
参考文章:
http://blog.chinaunix.net/uid-20384806-id-3055333.html
http://blog.youkuaiyun.com/whizchen/article/details/9286557
http://blog.youkuaiyun.com/gamecreating/article/details/7698719
http://www.cppblog.com/peakflys/archive/2012/07/24/184855.aspx
<think>好的,我现在需要回答用户关于C++mapunordered_map的区别以及它们的应用场景的问题。首先,我应该回顾一下已有的知识,并查看用户提供的引用资料,确保回答准确。 首先,用户提供了四个引用,其中引用[1][4]提到了mapunordered_map的主要区别在于底层实现性能特点。引用[4]指出,map使用红黑树,而unordered_map使用哈希表。这说明两者的内部结构不同,这会影响到它们的操作效率内存使用情况。 接下来,引用[4]还提到map是有序的,键值按顺序存储,而unordered_map是无序的。这是因为红黑树是一种自平衡的二叉搜索树,插入元素时会自动排序,而哈希表则是根据哈希函数将元素分布到不同的桶中,没有特定的顺序。因此,当需要有序访问键值时,应该选择map,否则unordered_map可能更高效。 关于性能,引用[4]说明,map的查找、插入删除操作的时间复杂度是$O(\log n)$,而unordered_map在平均情况下是$O(1)$,最坏情况下是$O(n)$。这让我想到,哈希表的性能依赖于哈希函数的质量冲突处理机制。如果哈希函数设计得当,冲突较少,unordered_map的平均性能会很好,但在有大量冲突时性能会下降。而红黑树由于是平衡树,性能稳定,但每次操作需要更多的比较。 引用[3]提供了一个unordered_map使用示例,展示了如何插入元素修改值,这说明unordered_map使用方式map类似,都支持下标操作符。不过,两者的底层实现不同,导致行为上的差异,比如元素的顺序重复键的处理。 现在,我需要将这些信息整理成结构化的回答,比较两者的区别,并给出适用场景。同时,用户要求生成相关问题,我需要根据内容提出相关的问题。 在组织回答时,应该先介绍两者的基本区别,比如底层实现、有序性、性能差异,然后分别列出各自的适用场景。例如,当需要有序存储或按范围查询时,选择map;当需要快速查找且不关心顺序时,使用unordered_map。 同时,需要注意用户提到的引用标注,确保每个引用的内容正确对应。例如,引用[4]提到内存占用,红黑树的节点结构可能占用更多内存,而哈希表可能需要较大的内存来减少冲突,这点需要明确说明。 最后,生成的相关问题应该围绕两者的比较,比如性能分析、选择依据、内存影响等,帮助用户进一步理解主题。 需要检查是否有遗漏点,比如哈希表的负载因子rehash操作,或者map的迭代器稳定性等。不过根据引用内容,可能不需要深入这些细节,但可以简要提及。 总结下来,回答的结构应该是: 1. 底层数据结构 2. 有序性 3. 时间复杂度 4. 内存占用 5. 适用场景 每个部分结合引用内容进行说明,并正确标注引用来源。最后生成相关问题,如两者的性能对比、如何选择、内存影响等。</think>### C++ `map` vs `unordered_map` 区别及适用场景 #### 1. **底层数据结构** - **`map`**:基于**红黑树**(一种自平衡二叉搜索树)实现,元素按键值严格排序[^4]。 - **`unordered_map`**:基于**哈希表**实现,元素通过哈希函数分布到不同的“桶”中,无序存储。 #### 2. **有序性** - **`map`**:键值按升序(或自定义排序规则)排列,支持中序遍历有序访问[^4]。 ```cpp map<int, string> m = {{3, "a"}, {1, "b"}}; // 遍历顺序:1→3 ``` - **`unordered_map`**:键值无序,遍历顺序取决于哈希函数桶的分布。 #### 3. **时间复杂度** | 操作 | `map` | `unordered_map` | |--------------|------------------|-----------------------| | 插入/删除/查找 | $O(\log n)$ | 平均$O(1)$,最坏$O(n)$ | | 范围查询(如`lower_bound`) | 支持(高效) | 不支持 | #### 4. **内存占用** - **`map`**:每个节点需额外存储父/子指针颜色标记(红黑树特性),内存占用较高[^4]。 - **`unordered_map`**:内存取决于桶的数量负载因子,可能因避免哈希冲突而预分配更多空间。 #### 5. **适用场景** - **选择`map`的情况**: - 需要键值**有序遍历**或**范围查询**(如`a ≤ key ≤ b`)。 - 要求**稳定的对数时间复杂度**,避免哈希冲突导致的性能波动。 - 示例:词频统计需按字母顺序输出时。 - **选择`unordered_map`的情况**: - 需要**快速查找/插入**,且不关心顺序。 - 键类型无天然顺序或哈希函数易于实现。 - 示例:缓存系统需快速根据键检索值。 #### 代码示例对比 ```cpp // map示例(有序) map<string, int> m = {{"z", 3}, {"a", 1}}; for (auto& p : m) cout << p.first; // 输出顺序:a → z // unordered_map示例(无序) unordered_map<string, int> um = {{"z", 3}, {"a", 1}}; for (auto& p : um) cout << p.first; // 输出顺序随机(如z → a) ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值