C++ 系列: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其插入过程是:
得到key
通过hash函数得到hash值
得到桶号(一般都为hash值对桶数求模)
存放key和value在桶内。

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

hash_map中直接地址用hash函数生成,解决冲突,用比较函数解决。
3.内存占用测试

测试代码:
测试条件window下,VS2015 C++。string为key, int 为value。
1.UnorderMap:
[cpp] view plain copy
#include <unordered_map>
#include <string>
#include <iostream>
#include <windows.h>
#include <psapi.h>
#pragma comment(lib,"psapi.lib")
using namespace std;
using namespace stdext;
void showMemoryInfo(void)
{
HANDLE handle = GetCurrentProcess();
PROCESS_MEMORY_COUNTERS pmc;
GetProcessMemoryInfo(handle, &pmc, sizeof(pmc));
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;
}

//define the class
/*-------------------------------------------*/
/*函数类
*作为hash_map的hash函数
*string没有默认的hash函数
*/
class str_hash {
public:
size_t operator()(const string& str) const
{
unsigned long __h = 0;
for (size_t i = 0; i < str.size(); i++)
__h = 5 * __h + str[i];
return size_t(__h);
}
};

/*-------------------------------------------*/
/*函数类
*作为hash_map的比较函数 )
*(查找的时候不同的key往往可能对用到相同的hash值
*/
class str_compare
{
public:
bool operator()(const string& str1, const string& str2)const
{
return str1 == str2;
}
};

struct CharLess : public binary_function<const string&, const string&, bool>
{
public:
result_type operator()(const first_argument_type& _Left, const second_argument_type& _Right) const
{
return(_Left.compare(_Right) < 0 ? true : false);
}
};

int main()
{

cout << "Test HashMap(unorder map) Memory Use Start..."<< endl;
// VC下自定义类型
unordered_map<string, int, hash_compare<string, CharLess> > CharHash;
for (int i = 0; i < 10000000; i++)
{
string key = to_string(i);
CharHash[key] = i;
}
cout << "Test HashMap(unorder map) Memory Use End." << endl;
showMemoryInfo();
while (1);
return 0;
}

2.map:
[cpp] view plain copy
#include <iostream>
#include <map>
#include <string>
#include <windows.h>
#include <psapi.h>
#pragma comment(lib,"psapi.lib")
using namespace std;
void showMemoryInfo(void)
{
HANDLE handle = GetCurrentProcess();
PROCESS_MEMORY_COUNTERS pmc;
GetProcessMemoryInfo(handle, &pmc, sizeof(pmc));
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;
}

int main()
{
cout << "Test Map(Red-Black Tree) Memory Use Start..." << endl;
// VC下自定义类型
//map<const char*, int, hash_compare<const char*, CharLess> > CharHash;
map<string, int> CharMap;
for (int i = 0; i < 10000000; i++)
{
string key = to_string(i);
CharMap[key] = i;
}
cout << "Test Map(Red-Black Tree) Memory Use End." << endl;
showMemoryInfo();
while (1);
return 0;
}

测试结果:
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写的这么好的文章):
http://blog.csdn.net/orzlzro/article/details/7099231
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
#include "stdafx.h"

// 存放过程:key->hash函数->hash值对桶数求模得到桶号(桶有值则解决冲突),存放key和value在桶内
// 取回过程:key->hash函数->hash值对桶数求模得到桶号(桶有值则解决冲突),比较桶内的key是否相等,
// 若不相等则返回空迭代器,否则返回迭代器。

// 1.hash_map为下面类型的key定义了hash寻址函数(用于从key到hash值)和哈希比较函数(用于解决冲突)。
//struct hash<char*>
//struct hash<const char*>
//struct hash<char>
//struct hash<unsigned char>
//struct hash<signed char>
//struct hash<short>
//struct hash<unsigned short>
//struct hash<int>
//struct hash<unsigned int>
//struct hash<long>
//struct hash<unsigned long>
// 内建的类型直接 hash_map<int, string> mymap;像普通map一样使用即可。

// 2.自定义hash函数和比较函数
//在声明自己的哈希函数时要注意以下几点:

//使用struct,然后重载operator().
//返回是size_t
//参数是你要hash的key的类型。
//函数是const类型的。

// 定义自己的比较函数:
//使用struct,然后重载operator().
//返回是bool
//参数是你要hash的key的类型的两个常量参数,用于比较。
//函数是const类型的。

// 自定义hash函数和比较函数的使用:
// hash_map<ClassA, string, hash_A, equal_A> hmap;

// 3.hash_map使用的常用函数

//hash_map的函数和map的函数差不多。具体函数的参数和解释,请参看:STL 编程手册:Hash_map,这里主要介绍几个常用函数。
//
//hash_map(size_type n) 如果讲究效率,这个参数是必须要设置的。n 主要用来设置hash_map 容器中hash桶的个数。
//桶个数越多,hash函数发生冲突的概率就越小,重新申请内存的概率就越小。n越大,效率越高,但是内存消耗也越大。
//
//const_iterator find(const key_type& k) const. 用查找,输入为键值,返回为迭代器。
//
//data_type& operator[](const key_type& k) . 这是我最常用的一个函数。因为其特别方便,可像使用数组一样使用。
//不过需要注意的是,当你使用[key ]操作符时,如果容器中没有key元素,这就相当于自动增加了一个key元素。
//因此当你只是想知道容器中是否有key元素时,你可以使用find。如果你希望插入该元素时,你可以直接使用[]操作符。
//
//insert 函数。在容器中不包含key值时,insert函数和[]操作符的功能差不多。但是当容器中元素越来越多,
//每个桶中的元素会增加,为了保证效率,hash_map会自动申请更大的内存,以生成更多的桶。因此在insert以后,
//以前的iterator有可能是不可用的。
//
//erase 函数。在insert的过程中,当每个桶的元素太多时,hash_map可能会自动扩充容器的内存。
//但在sgi stl中是erase并不自动回收内存。因此你调用erase后,其他元素的iterator还是可用的。

#include <hash_map>
#include <string>
#include <iostream>
using namespace std;
using namespace stdext;
//define the class
/*-------------------------------------------*/
/*函数类
*作为hash_map的hash函数
*string没有默认的hash函数
*/
class str_hash{
public:
size_t operator()(const string& str) const
{
unsigned long __h = 0;
for (size_t i = 0 ; i < str.size() ; i ++)
__h = 5*__h + str[i];
return size_t(__h);
}
};

/*-------------------------------------------*/
/*函数类
*作为hash_map的比较函数 )
*(查找的时候不同的key往往可能对用到相同的hash值
*/
class str_compare
{
public:
bool operator()(const string& str1,const string& str2)const
{return str1==str2;}
};

struct CharLess : public binary_function<const char*, const char*, bool>
{
public:
result_type operator()(const first_argument_type& _Left, const second_argument_type& _Right) const
{
return(strcmp(_Left, _Right) < 0 ? true : false);
}
};

int main()
{
// 内建类型
hash_map<int,string> myHashMap;
myHashMap[0] = "JesseCen";
myHashMap[1] = "OZZ";
hash_map<int,string>::iterator itrHash = myHashMap.find(0);
if(itrHash != myHashMap.end())
{
cout<<"My Name is:"<<itrHash->second.c_str()<<endl;
}

// VC下自定义类型
hash_map<const char*, int, hash_compare<const char*, CharLess> > CharHash;
CharHash["a"] = 123;
CharHash["b"] = 456;
hash_map<const char*, int, hash_compare<const char*, CharLess> >::iterator itrChar = CharHash.find("b");
if( itrChar != CharHash.end())
{
cout<<"The find number is:"<< itrChar->second<<endl;
}

return 0;
}

时间: 2024-07-31 00:30:51

C++ 系列:unordered_map的相关文章

C++ 头文件系列(unordered_map、unordered_set)

简介 很明显,这两个头文件分别是map.set头文件对应的unordered版本. 所以它们有一个重要的性质就是: 乱序 如何乱序 这个unorder暗示着,这两个头文件中类的底层实现----Hash. 也是因为如此,你才可以在声明这些unordered模版类的时候,传入一个自定义的哈希函数,准确的说是哈希函数子(hash function object). 具有相同相同哈希值的元素被放在同一个桶(bucket)中. 为何乱序 在提供映射.集合功能的情况下,侧重于元素的快速获取. 用树结构(红黑

[LeetCode] Two Sum系列

LeetCode 1. Two Sum Given an array of integers, find two numbers such that they add up to a specific target number. The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index

[Leetcode] Sum 系列

Sum 系列题解 Two Sum题解 题目来源:https://leetcode.com/problems/two-sum/description/ Description Given an array of integers, return indices of the two numbers such that they add up to a specific target. You may assume that each input would have exactly one sol

C++ STL之unordered_map

hash_map未加入C++11标准 C++11标准加入unordered系列的容器unordered_map map vs unordered_map: map底层实现为红黑树,时间复杂度为O(logn),unordered_map底层实现为哈希表,时间复杂度为O(1),均不能有重复的key,可使用[]运算符 但是在数据量小的时候,unorder_map比map慢 原因在于unordered_map的初始化比较耗时,我们都知道map是红黑树,unordered_map是哈希表,造成性能差异的原

【Windows10&nbsp;IoT开发系列】配置篇

原文:[Windows10 IoT开发系列]配置篇 Windows10 For IoT是Windows 10家族的一个新星,其针对不同平台拥有不同的版本.而其最重要的一个版本是运行在Raspberry Pi.MinnowBoard和Galileo平台上的核心版.本文重点针对Raspberry Pi平台的Windwos10 IoT配置做介绍. Windows 10 IoT Editions ​一:设置你的电脑. 注:​开发Windows10 IoT的电脑需要Visual Studio 2015.

【Windows10&nbsp;IoT开发系列】PowerShell的相关配置

原文:[Windows10 IoT开发系列]PowerShell的相关配置 可使用 Windows PowerShell 远程配置和管理任何 Windows 10 IoT 核心版设备.PowerShell 是基于任务的命令行 Shell 和脚本语言,专为进行系统管理而设计. 1.​启动 PowerShell (PS) 会话 注:若要使用装有Windows10 IoT Core设备启动PS会话,首先需要在主机电脑与设备之间创建信任关系. ​启动 Windows IoT 核心版设备后,与该设备相连的

【Windows10&nbsp;IoT开发系列】API&nbsp;移植工具

原文:[Windows10 IoT开发系列]API 移植工具 Windows 10 IoT Core 中是否提供你的当前 Win32 应用程序或库所依赖的 API? 如果不提供,是否存在可使用的等效 API? 此工具可以为你回答这些问题,并协助你将你的当前 Win32 应用程序和库迁移到 Windows IoT Core. Windows 10 IoT 核心版 API 移植工具可在 ms-iot/iot-utilities github 存储库中找到.下载存储库 zip 并将 IoTAPIPor

【Windows10&nbsp;IoT开发系列】“Hello,World!”指导

原文:[Windows10 IoT开发系列]"Hello,World!"指导 本文主要是介绍使用C#来开发一个可以运行在Raspberry Pi2上的一个基本项目. ​1.在启动Visual Studio 2015后,选择"文件"→"新建项目". ​在打开的"新建项目"对话框中,选择"通用". ​选择第一个项目"空白应用(通用Windows)" 新建项目 ​注:如果这是你创建的第一个项

【Windows10&nbsp;IoT开发系列】开发人员模式设置

原文:[Windows10 IoT开发系列]开发人员模式设置  声明:本文转自微软Windows 开发人员中心(https://msdn.microsoft.com/library/windows/apps/xaml/dn706236.aspx),在此基础上进行删减和修改. ​对于要用于开发.安装或测试应用的设备,不再需要开发人员许可证.你只需从设备的设置中为这些任务启用设备一次.(仅限于运行Windows 10系统的设备) 使用开发人员功能 ​使用 Microsoft Visual Stud