hash_map介绍与使用

0 概述

虽然hash_map和map都是STL的一部分,但是目前的C++标准(C++11)中只有map而没有hash_map,可以说STL只是部分包含于目前的C++标准中。主流的GNU C++和MSVC++出于编译器扩展的目的实现了hash_map,SGI有hash_map的实现,Boost也有类似于hash_map的unordered_map实现,google有dense hash_map和sparse hash_map两种实现(前者重时间效率,后者重空间效率)。罗列如下:

(1)SGI的hash_map:http://www.sgi.com/tech/stl/hash_map.html。本文中以SGI的hash_map为例子进行说明。

(2)GNU C++的hash_map:

https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.1/class____gnu__cxx_1_1hash__map.html

(3)MSVC++的hash_map:msdn.microsoft.com/en-us/library/bb398039.aspx(VS中自带的就是这个)

(4)Boost的unordered_map(头文件:<boost/unordered_map.hpp>):

http://www.boost.org/doc/libs/1_55_0/doc/html/boost/unordered_map.html

(5)google的sparsehash和densehash:http://code.google.com/p/sparsehash/source/browse/trunk/src/google/?r=98


1 与map定义对比

相同点:二者都是STL中的关联容器,且都具有以下两个性质:

a.键值形式,即元素类型都是键值形式:pair<const Key, Data>;

b.键唯一性,即没有两个元素有相同的键:key。

不同点:

hash_map是一种将对象的值data和键key关联起来的哈希关联容器(Hashed Associative Container),而map是一种将对象的值data和键key关联起来的排序关联容器(Sorted Associative Container)。所谓哈希关联容器是使用hash table实现的关联容器,它不同于一般的排序关联容器:哈希关联容器的元素没有特定的顺序,大部分操作的最差时间复杂度为O(n),平均时间复杂度为常数,所以在不要求排序而只要求存取的应用中,哈希关联容器的效率要远远高于排序关联容器。


2 与map实现对比

map的底层是用红黑树实现的,操作的时间复杂度是O(log(n))级别;hash_map的底层是用hash table实现的,操作的时间复杂度是常数级别


3 与map应用对比

在元素数量达到一定数量级时如果要求效率优先,则采用hash_map。但是注意:虽然hash_map 操作速度比map的速度快,但是hash函数以及解决冲突都需要额外的执行时间,且hash_map构造速度慢于map。其次,hash_map由于基于hash table,显然是空间换时间,因此hash_map对内存的消耗高于map。所以选择时需要权衡三个因素:速度,数据量,内存。


4 重点介绍hash_map

(1)hash_map原理

hash_map使用hash table来实现,首先分配内存,形成许多bucket用来存放元素,然后利用hash函数,对元素的key进行映射,存放到对应的bucket内。这其中hash函数用于定址,额外的比较函数用于解决冲突。该过程可以描述为:

a.计算元素的key

b.通过hash函数对key进行映射(常见的为取模),得到hash值,即为对应的bucket索引

c.存放元素的key和data在bucket内。

对应的查询过程是:

a.计算元素的key

b.通过hash函数对key进行映射(常见的为取模),得到hash值,即为对应的bucket索引

c.比较bucket内元素的key’与该key相等,若不相等则没有找到。

d.若相等则取出该元素的data。

所以实现hash_map最重要的两个东西就是hash函数和比较函数。以下以SGI的hash_map为例子进行说明。

(2)hash_map类定义

map构造时只需要比较函数(小于函数),hash_map构造时需要定义hash函数和比较函数(等于函数)。SGI中hash_map定义于stl_hash_map.h,定义为:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // Forward declaration of equality operator; needed for friend declaration.  
  2.   
  3. template <class _Key, class _Tp,  
  4.           class _HashFcn  __STL_DEPENDENT_DEFAULT_TMPL(hash<_Key>),  
  5.           class _EqualKey __STL_DEPENDENT_DEFAULT_TMPL(equal_to<_Key>),  
  6.           class _Alloc =  __STL_DEFAULT_ALLOCATOR(_Tp) >  
  7. class hash_map;  
  8.   
  9. ......  
  10.   
  11. template <class _Key, class _Tp, class _HashFcn, class _EqualKey,  
  12.           class _Alloc>  
  13. class hash_map  
  14. {  
  15. ......  
  16. }  

其中,参数1和参数2分别为键和值,参数3和参数4分别为hash函数和比较函数,实际上STL中使用结构体来封装这两个函数,用户可以自定义这两个结构体,也可以采用提供的默认值。参数5是hash_map的allocator,用于内部内存管理。


下面分三种情况说明这2个函数的使用:默认hash和比较函数,自定义hash函数,自定义比较函数。

(3)默认hash和比较函数

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // SGI hash_map definition  
  2. #include "hash_map.h"  
  3.   
  4. int main()  
  5. {  
  6.     //use class as Compare  
  7.     hash_map<const char*, int> months;  
  8.   
  9.     months["january"] = 31;  
  10.     months["february"] = 28;  
  11.     months["march"] = 31;  
  12.     months["april"] = 30;  
  13.     months["may"] = 31;  
  14.     months["june"] = 30;  
  15.     months["july"] = 31;  
  16.     months["august"] = 31;  
  17.     months["september"] = 30;  
  18.     months["october"] = 31;  
  19.     months["november"] = 30;  
  20.     months["december"] = 31;  
  21.   
  22.     return 0;  
  23. }  

从上面hash_map的定义可以看出,这里采用了默认的hash函数(hash<_Key>)和比较函数(equal_to<_Key>),对于这个例子:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. hash_map<const char*, int> months;  

就等同于

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. hash_map<const char*, int, hash<const char*>, equal_to< const char* >> months;  

(4)自定义hash函数

首先SGI的STL提供了这些默认的hash函数,均定义在stl_hash_fun.h中:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //默认hash函数  
  2. struct hash<char*>  
  3. struct hash<const char*>  
  4. struct hash<char>   
  5. struct hash<unsigned char>   
  6. struct hash<signed char>  
  7. struct hash<short>  
  8. struct hash<unsigned short>   
  9. struct hash<int>   
  10. struct hash<unsigned int>  
  11. struct hash<long>   
  12. struct hash<unsigned long>  

其次,自定义hash函数时,定义一个结构体,名字任意,结构体中重载operator(),参数为自定义键的类型的对象引用。在定义hash_map的时候,将该结构体传给第三个参数即可。假设自定义键的类型为KeyClass,则如下所示:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include "hash_map.h"  
  2.   
  3. struct my_hash  
  4. {  
  5.     size_t operator()(const KeyClass& x) const  
  6.     {  
  7.         ......  
  8.     }  
  9. };  
  10.   
  11. //hash_map定义  
  12. hash_map<KeyClass, ..., my_hash, ...> my_hash_map;  


基于上面默认的例子,自定义一个string类型的hash函数:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include "hash_map.h"  
  2.   
  3. //直接调用系统定义的字符串hash函数"__stl_hash_string":  
  4. struct str_hash  
  5. {  
  6.     size_t operator()(const string& str) const  
  7.     {  
  8.         return return __stl_hash_string(str.c_str());  
  9.     }  
  10. };  
  11. //或者自己写:  
  12. struct str_hash  
  13. {  
  14.     size_t operator()(const string& str) const  
  15.     {  
  16.         unsigned long __h = 0;  
  17.         for (size_t i = 0 ; i < str.size() ; i ++)  
  18.             __h = 5*__h + str[i];  
  19.         return size_t(__h);  
  20.     }  
  21. };  
  22.   
  23. //上面默认例子中的months可以改成:  
  24. hash_map<string, int, str_hash> my_months;  


(5)自定义比较函数

首先SGI的STL提供了默认的比较函数,定义在stl_function.h中:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //默认比较函数  
  2. template <class _Tp>  
  3. struct equal_to : public binary_function<_Tp,_Tp,bool>   
  4. {  
  5.   bool operator()(const _Tp& __x, const _Tp& __y) const { return __x == __y; }  
  6. };  
  7.   
  8. // binary_function函数声明  
  9. template <class _Arg1, class _Arg2, class _Result>  
  10. struct binary_function {  
  11.   typedef _Arg1 first_argument_type;  
  12.   typedef _Arg2 second_argument_type;  
  13.   typedef _Result result_type;  
  14. };  

其次,自定义比较函数时,有两种方法:重载operator==操作符实现元素(键值对)相等的比较;自定义比较函数结构体来重载operator()(与自定义hash函数类似)。

第一种方法,假设要比较的元素有两个字段iID和len:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. struct my_element  
  2. {  
  3.     int iID;  
  4.     int len;  
  5.     bool operator==(const my_element& e) const  
  6.     {  
  7.         return (iID==e.iID) && (len==e.len) ;  
  8.     }  
  9. };  

第二种方法:参数为两个自定义键类型的对象引用,在函数中实现对两个对象是否相等的比较。在定义hash_map的时候,将比较函数的结构体传给第四个参数即可。假设自定义键的类型为KeyClass,自定义hash函数结构体为my_hash ,自定义比较函数结构体为my_compare,则如下所示:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include "hash_map.h"  
  2.   
  3. struct my_ compare  
  4. {  
  5.     bool operator()(const KeyClass& x, const KeyClass& y) const  
  6.     {  
  7.         //比较x和y  
  8.     }  
  9. };  
  10.   
  11. //hash_map定义  
  12. hash_map<KeyClass, ..., my_hash, my_compare > my_hash_map;  


同样的,基于上面默认的例子,采用第二种方法自定义比较函数:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include "hash_map.h"  
  2.   
  3. //1.采用const char*作为键类型  
  4. //注意:这里可以直接采用默认的hash<const char*>函数  
  5.   
  6. //定义const char*的比较函数  
  7. struct str_compare  
  8. {  
  9.   bool operator()(const char* s1, const char* s2) const  
  10.   {  
  11.     return strcmp(s1, s2) == 0;  
  12.   }  
  13. };  
  14.   
  15. //上面默认例子中的months可以改成:  
  16. hash_map<const char*, int, hash<const char*>, str_compare> my_months;  
  17.   
  18. /  
  19.   
  20. //2.采用string作为键类型  
  21. //注意:这里需要同时定义string的hash函数  
  22. struct str_hash  
  23. {  
  24.     size_t operator()(const string& str) const  
  25.     {  
  26.         return return __stl_hash_string(str.c_str());  
  27.     }  
  28. };  
  29. //定义string的比较函数  
  30. struct str_compare  
  31. {  
  32.   bool operator()(const string& s1, const string& s2) const  
  33.   {  
  34.     return strcmp(s1.c_str(), s2.c_str()) == 0;  
  35.   }  
  36. };  
  37.   
  38. //上面默认例子中的months可以改成:  
  39. hash_map<string, int, str_hash, str_compare> my_months;  


(6)其他hash_map成员函数

hash_map的函数和map的函数差不多。具体函数的参数和解释,请参看SGI的hash_map介绍,这里主要介绍几个常用函数:

hash_map(size_type n):如果讲究效率,这个参数是必须要设置的。n 主要用来设置hash_map 容器中hash桶的个数。桶个数越多,hash函数发生冲突的概率就越小,重新申请内存的概率就越小。n越大,效率越高,但是内存消耗也越大。

const_iterator find(const key_type& k)const:用查找,输入为键值,返回为迭代器。

data_type& operator[](constkey_type& k) :像数组一样随机访问元素。注意:当使用[key]操作符时,如果容器中没有key元素,这就相当于自动增加了一个key元素(等同于插入操作)。所以如果只是想知道容器中是否有key元素时,可以使用find函数。

insert函数:在容器中不包含key值时,insert函数和[]操作符的功能差不多。但是当容器中元素越来越多,每个桶中的元素会增加,为了保证效率, hash_map会自动申请更大的内存,以生成更多的桶。因此在insert以后,以前的iterator有可能是不可用的。

erase 函数:在insert的过程中,当每个桶的元素太多时,hash_map可能会自动扩充容器的内存,但在SGI的STL中erase并不自动回收内存。因此你调用erase后,其他元素的iterator还是可用的。


最后如何具体使用hash_map就忽略了,可以参考详细解说STL hash_map系列[blog.163.com/liuruigong_lrg/blog/static/27370306200711334341781]


5 其他hash容器类

比如hash_set,hash_multimap,hash_multiset,这些容器与set,multimap,multiset的区别同hash_map与map的区别一样。


6 hash_map性能测试

【引用自:各类 C++ hashmap 性能测试总结_雨之絮语_百度空间

本文一开头提到了不同的hash_map的实现,这里测试各hash_map插入数据和查找数据两类操作的性能:设定hash_map的key 和 value 均使用 int,也就是 map<int, int>的形式。经过对比,插入 1000万次和查找1万次,各种实现的性能如下图:



(红圈部分为插入 1000万条记录需要的时间,绿圈部分是查找1万次需要的时间)

如上图所示,插入需要的时间大致在 1~4秒之间,查询所需要的时间比较少。对比各类实现的性能,boost::unordered_map 在综合性能上比较好。google::dense_hash_map在内存占用上非常出色,查找速度极快,插入速度比boost::unordered_map慢。至于 Visual Studio 2010 自带的std::map和std::hash_map的实现,只能用惨不忍睹来形容。

【备注】

测试环境:

CPU: Duo T6600

Memory: 4GB

软件版本:

Visual Studio 2010

boost 1.48.0

google sparsehash 1.11

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值