C++ STL中哈希表 hash_map从头到尾详细介绍

0 为什么需要hash_map

用过map吧?map提供一个很常用的功能,那就是提供key-value的存储和查找功能。例如,我要记录一个人名和相应的存储,而且随时增加,要快速查找和修改:

岳不群-华山派掌门人,人称君子剑
张三丰-武当掌门人,太极拳创始人
东方不败-第一高手,葵花宝典
...

这些信息如果保存下来并不复杂,但是找起来比较麻烦。例如我要找"张三丰"的信息,最傻的方法就是取得所有的记录,然后按照名字一个一个比较。如果要速度快,就需要把这些记录按照字母顺序排列,然后按照二分法查找。但是增加记录的时候同时需要保持记录有序,因此需要插入排序。考虑到效率,这就需要用到二叉树。讲下去会没完没了,如果你使用STL 的map容器,你可以非常方便的实现这个功能,而不用关心其细节。关于map的数据结构细节,感兴趣的朋友可以参看学习STL map, STL set之数据结构基础。看看map的实现:

#include <map>
#include <string>
using namespace std;
...
map<string, string> namemap;

//增加。。。
namemap["岳不群"]="华山派掌门人,人称君子剑";
namemap["张三丰"]="武当掌门人,太极拳创始人";
namemap["东方不败"]="第一高手,葵花宝典";
...

//查找。。
if(namemap.find("岳不群") != namemap.end()){
        ...
}

不觉得用起来很easy吗?而且效率很高,100万条记录,最多也只要20次的string.compare的比较,就能找到你要找的记录;200万条记录事,也只要用21次的比较。

速度永远都满足不了现实的需求。如果有100万条记录,我需要频繁进行搜索时,20次比较也会成为瓶颈,要是能降到一次或者两次比较是否有可能?而且当记录数到200万的时候也是一次或者两次的比较,是否有可能?而且还需要和map一样的方便使用。

答案是肯定的。这时你需要has_map. 虽然hash_map目前并没有纳入C++ 标准模板库中,但几乎每个版本的STL都提供了相应的实现。而且应用十分广泛。在正式使用hash_map之前,先看看hash_map的原理。

1 数据结构:hash_map原理

这是一节让你深入理解hash_map的介绍,如果你只是想囫囵吞枣,不想理解其原理,你倒是可以略过这一节,但我还是建议你看看,多了解一些没有坏处。

hash_map基于hash table(哈希表)。 哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,几乎可以看成是常数时间;而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。

其基本原理是:使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数,也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标,hash值)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素“分类”,然后将这个元素存储在相应“类”所对应的地方,称为桶。

但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。 总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。

hash_map,首先分配一大片内存,形成许多桶。是利用hash函数,对key进行映射到不同区域(桶)进行保存。其插入过程是:

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

其取值过程是:

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

hash_map中直接地址用hash函数生成,解决冲突,用比较函数解决。这里可以看出,如果每个桶内部只有一个元素,那么查找的时候只有一次比较。当许多桶内没有值时,许多查询就会更快了(指查不到的时候).

由此可见,要实现哈希表, 和用户相关的是:hash函数和比较函数。这两个参数刚好是我们在使用hash_map时需要指定的参数。

2 hash_map 使用

2.1 一个简单实例

不要着急如何把"岳不群"用hash_map表示,我们先看一个简单的例子:随机给你一个ID号和ID号相应的信息,ID号的范围是1~2的31次方。如何快速保存查找。

#include <hash_map>
#include <string>
using namespace std;
int main(){
        hash_map<int, string> mymap;
        mymap[9527]="唐伯虎点秋香";
        mymap[1000000]="百万富翁的生活";
        mymap[10000]="白领的工资底线";
        ...
        if(mymap.find(10000) != mymap.end()){
                ...
        }

够简单,和map使用方法一样。这时你或许会问?hash函数和比较函数呢?不是要指定么?你说对了,但是在你没有指定hash函数和比较函数的时候,你会有一个缺省的函数,看看hash_map的声明,你会更加明白。下面是SGI STL的声明:

template <class _Key, class _Tp, class _HashFcn = hash<_Key>,
class _EqualKey = equal_to<_Key>,
class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class hash_map
{
        ...
}

也就是说,在上例中,有以下等同关系:

...
hash_map<int, string> mymap;
//等同于:
hash_map<int, string, hash<int>, equal_to<int> > mymap;

Alloc我们就不要取关注太多了(希望深入了解Allocator的朋友可以参看标准库 STL :Allocator能做什么)

2.2 hash_map 的hash函数

hash< int>到底是什么样子?看看源码:

struct hash<int> {
        size_t operator()(int __x) const { return __x; }
};

原来是个函数对象。在SGI STL中,提供了以下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>

也就是说,如果你的key使用的是以上类型中的一种,你都可以使用缺省的hash函数。当然你自己也可以定义自己的hash函数。对于自定义变量,你只能如此,例如对于string,就必须自定义hash函数。例如:

struct str_hash{
        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函数,你可以这样写:
struct str_hash{
        size_t operator()(const string& str) const
        {
                return __stl_hash_string(str.c_str());
        }
};

在声明自己的哈希函数时要注意以下几点:

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

如果这些比较难记,最简单的方法就是照猫画虎,找一个函数改改就是了。

现在可以对开头的"岳不群"进行哈希化了 smile . 直接替换成下面的声明即可:

map<string, string> namemap; 
//改为:
hash_map<string, string, str_hash> namemap;

其他用法都不用边。当然不要忘了吧str_hash的声明以及头文件改为hash_map。

你或许会问:比较函数呢?别着急,这里就开始介绍hash_map中的比较函数。

2.3 hash_map 的比较函数

在map中的比较函数,需要提供less函数。如果没有提供,缺省的也是less< Key> 。在hash_map中,要比较桶内的数据和key是否相等,因此需要的是是否等于的函数:equal_to< Key> 。先看看equal_to的源码:

//本代码可以从SGI STL
//先看看binary_function 函数声明,其实只是定义一些类型而已。
template <class _Arg1, class _Arg2, class _Result>
struct binary_function {
        typedef _Arg1 first_argument_type;
        typedef _Arg2 second_argument_type;
        typedef _Result result_type;
};
//看看equal_to的定义:
template <class _Tp>
struct equal_to : public binary_function<_Tp,_Tp,bool>
{
        bool operator()(const _Tp& __x, const _Tp& __y) const { return __x == __y; }
};

如果你使用一个自定义的数据类型,如struct mystruct, 或者const char* 的字符串,如何使用比较函数?使用比较函数,有两种方法. 第一种是:重载==操作符,利用equal_to;看看下面的例子:

struct mystruct{
        int iID;
        int  len;
        bool operator==(const mystruct & my) const{
                return (iID==my.iID) && (len==my.len) ;
        }
};

这样,就可以使用equal_to< mystruct>作为比较函数了。另一种方法就是使用函数对象。自定义一个比较函数体:

struct compare_str{
        bool operator()(const char* p1, const char*p2) const{
                return strcmp(p1,p2)==0;
        }
};

有了compare_str,就可以使用hash_map了。

typedef hash_map<const char*, string, hash<const char*>, compare_str> StrIntMap;
StrIntMap namemap;
namemap["岳不群"]="华山派掌门人,人称君子剑";
namemap["张三丰"]="武当掌门人,太极拳创始人";
namemap["东方不败"]="第一高手,葵花宝典";

2.4 hash_map 函数

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

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

 

3 相关hash容器

hash 容器除了hash_map之外,还有hash_set, hash_multimap, has_multiset, 这些容器使用起来和set, multimap, multiset的区别与hash_map和map的区别一样,我想不需要我一一细说了吧。

4 其他

这里列几个常见问题,应该对你理解和使用hash_map比较有帮助。

4.1 hash_map和map的区别在哪里?

  • 构造函数。hash_map需要hash函数,等于函数;map只需要比较函数(小于函数).
  • 存储结构。hash_map采用hash表存储,map一般采用红黑树(RB Tree)实现。因此其memory数据结构是不一样的。

4.2 什么时候需要用hash_map,什么时候需要用map?

总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n)小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且hash_map的构造速度较慢。

现在知道如何选择了吗?权衡三个因素: 查找速度, 数据量, 内存使用。

这里还有个关于hash_map和map的小故事,看看:http://dev.csdn.net/Develop/article/14/14019.shtm

 

4.3 如何在hash_map中加入自己定义的类型?

你只要做两件事, 定义hash函数,定义等于比较函数。下面的代码是一个例子:

-bash-2.05b$ cat my.cpp
#include <hash_map>
#include <string>
#include <iostream>

using namespace std;
//define the class
class ClassA{
        public:
        ClassA(int a):c_a(a){}
        int getvalue()const { return c_a;}
        void setvalue(int a){c_a;}
        private:
        int c_a;
};

//1 define the hash function
struct hash_A{
        size_t operator()(const class ClassA & A)const{
                //  return  hash<int>(classA.getvalue());
                return A.getvalue();
        }
};

//2 define the equal function
struct equal_A{
        bool operator()(const class ClassA & a1, const class ClassA & a2)const{
                return  a1.getvalue() == a2.getvalue();
        }
};

int main()
{
        hash_map<ClassA, string, hash_A, equal_A> hmap;
        ClassA a1(12);
        hmap[a1]="I am 12";
        ClassA a2(198877);
        hmap[a2]="I am 198877";
        
        cout<<hmap[a1]<<endl;
        cout<<hmap[a2]<<endl;
        return 0;
}
-bash-2.05b$ make my
c++  -O -pipe -march=pentiumpro  my.cpp  -o my
-bash-2.05b$ ./my
I am 12
I am 198877

4.4如何用hash_map替换程序中已有的map容器?

这个很容易,但需要你有良好的编程风格。建议你尽量使用typedef来定义你的类型:

typedef map<Key, Value> KeyMap;

当你希望使用hash_map来替换的时候,只需要修改:

typedef hash_map<Key, Value> KeyMap;

其他的基本不变。当然,你需要注意是否有Key类型的hash函数和比较函数。

4.5为什么hash_map不是标准的?

具体为什么不是标准的,我也不清楚,有个解释说在STL加入标准C++之时,hash_map系列当时还没有完全实现,以后应该会成为标准。如果谁知道更合理的解释,也希望告诉我。但我想表达的是,正是因为hash_map不是标准的,所以许多平台上安装了g++编译器,不一定有hash_map的实现。我就遇到了这样的例子。因此在使用这些非标准库的时候,一定要事先测试。另外,如果考虑到平台移植,还是少用为佳。

 

 

常见问题:

 

本来想用hash_map实现大数量的快速查找,后来发现效率并不快,而且有些问题也很不解,比如看如下代码:

C/C++ code
#include <iostream> #include <hash_map.h> using namespace std; int main(){ hash_map<int,string> hm(3); //初始化hash_map的桶的个数 hm.insert(make_pair(0,"hello")); hm.insert(make_pair(1,"ok")); hm.insert(make_pair(2,"bye")); hm.insert(make_pair(3,"world")); cout<<hm.size()<<endl; cout<<hm.bucket_count()<<endl; return 0; }


输出结果:
4
53
对这个结果很疑惑,明明我定义了桶的个数,为什么后面得到桶的个数为53?
hash_map默认对int类型的Key如何hash,hash函数是什么?
如何使得查找能更高效?可以用空间来换

各位大侠请教啊

 

这是我对hash的曾经的一点尝试,仅供参考:

C/C++ code
#include <iostream> #include <map> #include <string> #ifdef __GNUC__ #include <ext/hash_map> #else #include <hash_map> #endif #ifdef __GXX_EXPERIMENTAL_CXX0X__ #include <unordered_map> #endif namespace std { using namespace __gnu_cxx; } namespace __gnu_cxx { template<> struct hash< std::string > { size_t operator()( const std::string& x ) const { return hash< const char* >()(x.c_str()); } }; } int main() { std::map<std::string, std::string> stdMap; stdMap["_GLIBCXX_STD"] = "std"; stdMap["_GLIBCXX_BEGIN_NESTED_NAMESPACE"] = "+namespace"; stdMap["_GLIBCXX_BEGIN_NAMESPACE"] = "+namespace"; stdMap["_GLIBCXX_END_NESTED_NAMESPACE"] = "}"; stdMap["_GLIBCXX_END_NAMESPACE"] = "}"; stdMap["_GLIBCXX_END_NAMESPACE_TR1"] = "}"; stdMap["_GLIBCXX_BEGIN_NAMESPACE_TR1"] = "-namespace tr1 {"; stdMap["_GLIBCXX_STD2"] = "2std"; stdMap["_GLIBCXX_BEGIN_NESTED_NAMESPACE2"] = "2+namespace"; stdMap["_GLIBCXX_BEGIN_NAMESPACE2"] = "2+namespace"; stdMap["_GLIBCXX_END_NESTED_NAMESPACE2"] = "2}"; stdMap["_GLIBCXX_END_NAMESPACE2"] = "2}"; stdMap["_GLIBCXX_END_NAMESPACE_TR12"] = "2}"; stdMap["_GLIBCXX_BEGIN_NAMESPACE_TR12"] = "2-namespace tr1 {"; stdMap["_XXGLIBCXX_END_NAMESPACE_TR12"] = "X2}"; stdMap["_XXGLIBCXX_BEGIN_NAMESPACE_TR12"] = "X2-namespace tr1 {"; std::hash_map<std::string, std::string> hashMap; hashMap["_GLIBCXX_STD"] = "std"; hashMap["_GLIBCXX_BEGIN_NESTED_NAMESPACE"] = "+namespace"; hashMap["_GLIBCXX_BEGIN_NAMESPACE"] = "+namespace"; hashMap["_GLIBCXX_END_NESTED_NAMESPACE"] = "}"; hashMap["_GLIBCXX_END_NAMESPACE"] = "}"; hashMap["_GLIBCXX_END_NAMESPACE_TR1"] = "}"; hashMap["_GLIBCXX_BEGIN_NAMESPACE_TR1"] = "-namespace tr1 {"; hashMap["_GLIBCXX_STD2"] = "2std"; hashMap["_GLIBCXX_BEGIN_NESTED_NAMESPACE2"] = "2+namespace"; hashMap["_GLIBCXX_BEGIN_NAMESPACE2"] = "2+namespace"; hashMap["_GLIBCXX_END_NESTED_NAMESPACE2"] = "2}"; hashMap["_GLIBCXX_END_NAMESPACE2"] = "2}"; hashMap["_GLIBCXX_END_NAMESPACE_TR12"] = "2}"; hashMap["_GLIBCXX_BEGIN_NAMESPACE_TR12"] = "2-namespace tr1 {"; hashMap["_XXGLIBCXX_END_NAMESPACE_TR12"] = "X2}"; hashMap["_XXGLIBCXX_BEGIN_NAMESPACE_TR12"] = "X2-namespace tr1 {"; #ifdef __GXX_EXPERIMENTAL_CXX0X__ std::unordered_map<std::string, std::string> unorderedMap; unorderedMap["_GLIBCXX_STD"] = "std"; unorderedMap["_GLIBCXX_BEGIN_NESTED_NAMESPACE"] = "+namespace"; unorderedMap["_GLIBCXX_BEGIN_NAMESPACE"] = "+namespace"; unorderedMap["_GLIBCXX_END_NESTED_NAMESPACE"] = "}"; unorderedMap["_GLIBCXX_END_NAMESPACE"] = "}"; unorderedMap["_GLIBCXX_END_NAMESPACE_TR1"] = "}"; unorderedMap["_GLIBCXX_BEGIN_NAMESPACE_TR1"] = "-namespace tr1 {"; unorderedMap["_GLIBCXX_STD2"] = "2std"; unorderedMap["_GLIBCXX_BEGIN_NESTED_NAMESPACE2"] = "2+namespace"; unorderedMap["_GLIBCXX_BEGIN_NAMESPACE2"] = "2+namespace"; unorderedMap["_GLIBCXX_END_NESTED_NAMESPACE2"] = "2}"; unorderedMap["_GLIBCXX_END_NAMESPACE2"] = "2}"; unorderedMap["_GLIBCXX_END_NAMESPACE_TR12"] = "2}"; unorderedMap["_GLIBCXX_BEGIN_NAMESPACE_TR12"] = "2-namespace tr1 {"; unorderedMap["_XXGLIBCXX_END_NAMESPACE_TR12"] = "X2}"; unorderedMap["_XXGLIBCXX_BEGIN_NAMESPACE_TR12"] = "X2-namespace tr1 {"; #endif for (int i = 0; i < 5; ++i) { const clock_t t = clock(); for (int j = 0; j < 1000000; ++j) stdMap.find("testfindkey"); std::cout << "stdMap " << i + 1 << " : " << clock() - t << std::endl; } std::cout << "/n---------------/n" << std::endl; for (int i = 0; i < 5; ++i) { const clock_t t = clock(); for (int j = 0; j < 1000000; ++j) hashMap.find("testfindkey"); std::cout << "hashMap " << i + 1 << " : " << clock() - t << std::endl; } #ifdef __GXX_EXPERIMENTAL_CXX0X__ std::cout << "/n---------------/n" << std::endl; for (int i = 0; i < 5; ++i) { const clock_t t = clock(); for (int j = 0; j < 1000000; ++j) unorderedMap.find("testfindkey"); std::cout << "unorderedMap " << i + 1 << " : " << clock() - t << std::endl; } #endif return 0; }
如果你使用的vc自带的hash函数,那么它的定义中如下:
C/C++ code
template<class _Kty, class _Pr = less> class hash_compare1 { // traits class for hash containers public: //const static long lBucketSize = 0; enum { // parameters for hash table bucket_size = 4, // 0 < bucket_size min_buckets = 8 // min_buckets = 2 ^^ N, 0 < N }; 。。。

每次增长会2倍增加预分配内存,你的hash_map是哪个版本的?

先看看alvin_lee 朋友做的解析,我觉得还是很正确的,从算法角度阐述了他们之间的问题!


实际上这个问题不光C++会遇到,其他所有语言的标准容器的实现及选择上都是要考虑的。做应用程序你可能觉得影响不大,但是写算法或者核心代码就要小心了。今天改进代码,顺便又来温习基础功课了。

 

  还记得Herb Sutter那极有味道的《C++对话系列》么,在其中《产生真正的hash对象》这个故事里就讲了map的选择。顺便回顾一下,也讲一下我在实用中的理解。

  选择map容器,是为了更快的从关键字查找到相关的对象。与使用list这样的线性表容器相比,一可以简化查找的算法,二可以使任意的关键字做索引,并与目标对象配对,优化查找算法。在C++的STL中map是使用树来做查找算法,这种算法差不多相当与list线性容器的折半查找的效率一样,都是O (log2N),而list就没有map这样易定制和操作了。

  相比hash_map,hash_map使用hash表来排列配对,hash表是使用关键字来计算表位置。当这个表的大小合适,并且计算算法合适的情况下,hash表的算法复杂度为O(1)的,但是这是理想的情况下的,如果hash表的关键字计算与表位置存在冲突,那么最坏的复杂度为O(n)。

  那么有了这样的认识,我们应该怎么样选用算法呢?前两天看Python文章的时候,不知道哪个小子说Python的map比c++的map快,如何如何的。但是他并不知道Python是默认使用的 hash_map,而且这些语言特征本质上是使用c/c++写出来的,问题在与算法和手段,而不是在于语言本身的优劣,你熟悉了各种算法,各种语言的细节、设计思想,还能在这偏激的嚷嚷孰好孰坏(片面与偏激的看待事物只能表明愚昧与无知,任何事物都有存在的价值,包括技术)。显然C++的STL默认使用树结构来实现map,是有考究的。

  树查找,在总查找效率上比不上hash表,但是它很稳定,它的算法复杂度不会出现波动。在一次查找中,你可以断定它最坏的情况下其复杂度不会超过O(log2N)。而hash表就不一样,是O(1),还是O(N),或者在其之间,你并不能把握。假若你在开发一个供外部调用的接口,其内部有关键字的查找,但是这个接口调用并不频繁,你是会希望其调用速度快、但不稳定呢,还是希望其调用时间平均、且稳定呢。反之假若你的程序需要查找一个关键字,这个操作非常频繁,你希望这些操作在总体上的时间较短,那么hash表查询在总时间上会比其他要短,平均操作时间也会短。这里就需要权衡了。

  这里总结一下,选用map还是hash_map,关键是看关键字查询操作次数,以及你所需要保证的是查询总体时间还是单个查询的时间。如果是要很多次操作,要求其整体效率,那么使用hash_map,平均处理时间短。如果是少数次的操作,使用 hash_map可能造成不确定的O(N),那么使用平均处理时间相对较慢、单次处理时间恒定的map,考虑整体稳定性应该要高于整体效率,因为前提在操作次数较少。如果在一次流程中,使用hash_map的少数操作产生一个最坏情况O(N),那么hash_map的优势也因此丧尽了。


下面先看一段代码,从Codeproject的 Jay Kint:


// familiar month example used 
// mandatory contrived example to show a simple point
// compiled using MinGW gcc 3.2.3 with gcc -c -o file.o 
// file.cpp

#include <string>
#include <ext/hash_map>
#include <iostream>

using namespace std;
// some STL implementations do not put hash_map in std
using namespace __gnu_cxx;

hash_map<const char*, int> days_in_month;

class MyClass {
   static int totalDaysInYear;
public:
   void add_days( int days ) { totalDaysInYear += days; }
   static void printTotalDaysInYear(void) 
   { 
       cout << "Total Days in a year are " 
           << totalDaysInYear << endl; 
   }
};

int MyClass::totalDaysInYear = 0;

int main(void)
{
   days_in_month["january"] = 31;
   days_in_month["february"] = 28;
   days_in_month["march"] = 31;
   days_in_month["april"] = 30;
   days_in_month["may"] = 31;
   days_in_month["june"] = 30;
   days_in_month["july"] = 31;
   days_in_month["august"] = 31;
   days_in_month["september"] = 30;
   days_in_month["october"] = 31;
   days_in_month["november"] = 30;
   days_in_month["december"] = 31;

   // ERROR: This line doesn't compile.
   accumulate( days_in_month.begin(), days_in_month.end(),
       mem_fun( &MyClass::add_days ));

   MyClass::printTotalDaysInYear();

   return 0;
}

 

当然上面的代码完全可以使用STL来实现:


引用

Standard C++ Solutions
The Standard C++ Library defines certain function adaptors, select1st, select2nd and compose1, that can be used to call a single parameter function with either the key or the data element of a pair associative container.

select1st and select2nd do pretty much what their respective names say they do. They return either the first or second parameter from a pair.

compose1 allows the use of functional composition, such that the return value of one function can be used as the argument to another. compose1(f,g) is the same as f(g(x)).

Using these function adaptors, we can use for_each to call our function.

hash_map my_map;
for_each( my_map.begin(), my_map.end(), 
         compose1( mem_fun( &MyType::do_something ), 
                   select2nd                    MyType>::value_type>()));
Certainly, this is much better than having to define helper functions for each pair, but it still seems a bit cumbersome, especially when compared with the clarity that a comparable for loop has.

for( hash_map::iterator i = 
        my_map.begin();
    i != my_map.end(), ++i ) {

    i->second.do_something();
}
Considering it was avoiding the for loop for clarity's sake that inspired the use of the STL algorithms in the first place, it doesn't help the case of algorithms vs. hand written loops that the for loop is more clear and concise.

with_data and with_key
with_data and with_key are function adaptors that strive for clarity while allowing the easy use of the STL algorithms with pair associative containers. They have been parameterized much the same way mem_fun has been. This is not exactly rocket science, but it is quickly easy to see that they are much cleaner than the standard function adaptor expansion using compose1 and select2nd.

Using with_data and with_key, any function can be called and will use the data_type or key_type as the function's argument respectively. This allows hash_map, map, and any other pair associative containers in the STL to be used easily with the standard algorithms. It is even possible to use it with other function adaptors, such as mem_fun.

hash_map my_vert_buffers;

void ReleaseBuffers(void)
{
   // release the vertex buffers created so far.
   std::for_each( my_vert_buffers.begin(), 
       my_vert_buffers.end(), 
       with_data( boost::mem_fn( 
           &IDirect3DVertexBuffer9::Release )));
}
Here boost::mem_fn is used instead of mem_fun since it recognizes the __stdcall methods used by COM, if the BOOST_MEM_FN_ENABLE_STDCALL macro is defined.

 

另外添加一些实战的例子:
连接是:
http://blog.sina.com.cn/u/4755b4ee010004hm

摘录如下:


引用

一直都用的STL的map,直到最近库里数据量急剧增大,听别的做检索的同学说到hash_map,一直都打算换回来,今天好好做了个实验测试了哈hash_map的功能,效果以及与map比较的性能.
     首先,要说的是这两种数据结构的都提供了KEY-VALUE的存储和查找的功能.但是实现是不一样的,map是用的红黑树,查询时间复杂度为log (n),而hash_map是用的哈希表.查询时间复杂度理论上可以是常数,但是消耗内存大,是一种以存储换时间的方法.
     就应用来说,map已经是STL标准库的东西,可是hash_map暂时还未进入标准库,但也是非常常用也非常重要的库.
     这次所做的测试是对于100W及的文件列表,去重的表现,即是对文件名string,做map!
用到的头文件:

 


#include <time.h>    //计算时间性能用
     #include <ext/hash_map>   //包含hash_map 的头文件
     #include <map>             //stl的map
     using namespace std;        //std 命名空间
     using namespace __gnu_cxx;    //而hash_map是在__gnu_cxx的命名空间里的

//测试3个环节:用map的效率,hash_map系统hash函数的效率及自写hash函数的效率.

    11 struct str_hash{      //自写hash函数
    12     size_t operator()(const string& str) const
    13     {
    14         unsigned long __h = 0;
    15         for (size_t i = 0 ; i < str.size() ; i ++)
    16         {
    17             __h = 107*__h + str[i];
    18         }
    19         return size_t(__h);
    20     }
    21 };

    23 //struct str_hash{    //自带的string hash函数
    24 //        size_t operator()(const string& str) const
    25 //      {
    26 //          return __stl_hash_string(str.c_str());
    27 //      }
    28 //};

    30 struct str_equal{      //string 判断相等函数
    31     bool operator()(const string& s1,const string& s2) const
    32     {
    33         return s1==s2;
    34     }
    35 };

//用的时候
    37 int main(void)
    38 {
    39     vector<string> filtered_list;
    40     hash_map<string,int,str_hash,str_equal> file_map;
    41     map<string,int> file2_map;
    42     ifstream in("/dev/shm/list");
    43     time_t now1 = time(NULL);
    44     struct tm * curtime;
    45     curtime = localtime ( &now1 );
    46     cout<<now1<<endl;
    47     char ctemp[20];
    48     strftime(ctemp, 20, "%Y-%m-%d %H:%M:%S" , curtime);
    49     cout<<ctemp<<endl;
    50     string temp;
    51     int i=0;
    52     if(!in)
    53     {
    54         cout<<"open failed!~"<<endl;
    55     }
    56     while(in>>temp)
    57     {
    58         string sub=temp.substr(0,65);
    59         if(file_map.find(sub)==file_map.end())
    60 //      if(file2_map.find(sub)==file2_map.end())
    61         {
    62             file_map[sub]=i;
    63 //          file2_map[sub]=i;
    64             filtered_list.push_back(temp);
    65             i++;
    66 //          cout<<sub<<endl;
    67         }
    68     }
    69     in.close();
    70     cout<<"the total unique file number is:"<<i<<endl;
    71     ofstream out("./file_list");
    72     if(!out)
    73     {
    74         cout<<"failed open"<<endl;
    75     }
    76     for(int j=0;j<filtered_list.size();j++)
    77     {
    78         out<<filtered_list[j]<<endl;
    79     }
    80     time_t now2=time(NULL);
    81     cout<<now2<<endl;
    82     curtime = localtime ( &now2 );
    83     strftime(ctemp, 20, "%Y-%m-%d %H:%M:%S" , curtime);
    84     cout<<now2-now1<<"\t"<<ctemp<<endl;
    85     return 0;
    86 }

 


引用

得出来的结论是:(文件list有106W,去重后有51W)
   1.map完成去重耗时34秒
   2.hash_map用系统自带的函数,耗时22秒
   3.hash_map用自己写的函数,耗时14秒
      测试结果充分说明了hash_map比map的优势,另外,不同的hash函数对性能的提升也是不同的,上述hash函数为一同学,测试N多数据后得出的经验函数.
可以预见,当数量级越大时越能体现出hash_map的优势来!~

 

当然最后作者的结论是错误的,hash_map的原理理解错误!从第一个朋友的回答就可以体会到这个问题!

最后对于C++Builder用户,应该通过以下方法添加:
#include "stlport\hash_map"
才可以正确的使用hash_map

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/skyremember/archive/2008/09/18/2941076.aspx


一个简单的例子

/*
 *用来测试STL hash_map 
 *简单例子2008.5.5
*/
#include  
<cstdlib>
#include  
<iostream>
#include  
<string>
#include  
<hash_map.h>/*因为hash_map暂不为CPP标准所以没办法写为<hash_map>*/
/*-------------------------------------------*/
using  std::cout;
using  std::endl;
using  std::string;
/*-------------------------------------------*/
/*函数类
 *作为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;}
};
/*-------------------------------------------*/
int 
main(
int argc, char *argv[])
{  
    hash_map
<string,string,str_hash,str_compare>  myhash;
    
    myhash[
"google"]="newplan";
   
    myhash[
"baidu"]="zhaoziming";
   
    
if(myhash.find("google")!=myhash.end())
      cout
<<myhash["google"]<<endl;
    
    system(
"PAUSE");
    
    
return EXIT_SUCCESS;
}
/*-------------------------------------------*/


另一个简单例子

hash_map

代码实现:

#include

#include

#include

 

using namespace __gnu_cxx;

using namespace std;

 

struct str_hash{

    size_t operator()(const string& str) const {

        return __stl_hash_string(str.c_str());

    }

};

 

struct compare_str {

    bool operator()(const string& str1,const string& str2) const{

        return str1 == str2;

    }

};

 

int main()

{

    typedef hash_map namehash;

    namehash strhash;

    namehash::iterator it;

 

    //通过[]方式添加hash_map

    strhash["岳不群"]="华山派掌门人,人称君子剑";

    strhash["张三丰"]="武当掌门人,太极拳创始人";

    strhash["东方不败"]="第一高手,葵花宝典";

 

    //通过pair方式插入hash_map

    strhash.insert(pair("IntPassion", "林元已于,风而存在"));

    strhash.insert(make_pair("陈英俊", "玉树临风,英俊潇洒"));

 

    //通过find方式查看hash_map的元素

    it = strhash.find("陈英俊");

    cout<<it->first<<" -> "<<it->second<<endl;

 

    //通过[]方式获取hash_map元素

    cout<<"IntPassion -> "<<strhash["IntPassion"]<<endl;

 

    cout<<"\n遍历输出hash_map\n";

    for(namehash::iterator itb = strhash.begin(); itb!=strhash.end();itb++)

        cout<<itb->first<<" -> "<<itb->second<<endl;

 

    return 0;

}

 

从性能上来说,const char* 作为键要比string作为键要高;(后面的一篇文章会专门说明性能测试的结果)但是,使用string作为键更安全。使用const char*作为键的时候,要切记const char*指向的位置总是长期有效的。(尤其不要把一个栈内的字符数组加到全局的hash_map中作为键)

 

在这里几点要非常的注意,否则则会像我在编写该程序的时候,很简单但是却一直编译错误。

首先,要注意的是包含的头文件。hash_map不是C++标准库的一部分,但因其重要性很多库(sgi stlboost)实现了hash_map,包括g++编译器所带的头文件也包含了hash_map的实现代码(其实现为sgi stl的版本),其在include/ext目录下,该目录还包含了hash_setrope等的实现。,hash_map定义在__gnu_cxx命名空间中,故你必须在使用时限定名字空间__gnu_cxx::hash_map,或者使用using关键字,如下例:

#include

using namespace __gnu_cxx;

 

windows  linux下引入hash_sethash_map头文件

推荐使用方法:在源代码的前面写入一下代码:

// just for "#include " in linux

#if __GNUC__>2

#include

#include

using namespace __gnu_cxx;

#else

#include

#include

using namespace stdext;

#endif

其它解释和方法:

因为hash_map以前不属于标准库,而是后来引入的。所以在windows下需要使用stlport,然后在setting中加入Additional library path。在linux下使用gcc的时候,引入,使用的时候也说找不到hash_map,而这种后来引入标准库的有两种可能:一种是它被放在了stdext名空间里,那么就要使用using namespace stdext引入该名空间并#include ;另一种可能就是它被放在标准库的ext目录底下,这时就仍旧需要使用属于std名空间,这时你的源文件应当#include ;

 

其次,这里就是hash_maphash_set实现上的注意事项。因为,在hash_map中,键类型的hash函数实现只是对于一些基本的类型,char*ntchar并没有实现stringdouble

float这些类型了,所以要这对string类型必须实现hash函数(第三项于结构体或者自定义类型,则必须还要实现比较函数(第四项)。说实话,使用string类型作为键有更好的扩展性和应用性。比使用char*更加的灵活和方便。

       最后,比较重要的一点。上面实现的hash函数和比较函数都是const函数,并且函数的参数也是const类型的,如果少了其中一个都是无法编译通过的。


第3个简单例子

//hash_map,map,都是将记录型的元素划分为键值和映照数据两个部分;
//不同的是:hash_map采用哈希表的结构而map采用红黑树的结构;
//hash_map键值比较次数少,占用较多的空间,遍历出来的元素是非排序的而map是排序的

#include<hash_map>
#include<iostream>
using namespace std;

int main(void)
{
 hash_map<const char*,float>hm;
 //元素的插入:pair<iterator,bool>insert<const value_type &v>
 //insert(inputiterator first,inputiterator last)
 hm["apple"]=1.0f;
 hm["pear"]=1.5f;
 hm["orange"]=2.0f;
 hm["banana"]=1.8f;
 //元素的访问可以用,数组的方式也可以用迭代器的方式
 hash_map<const char*,float>::iterator i,iend,j;
 iend=hm.end();
 for(i=hm.begin();i!=iend;i++)
 {
  cout<<(*i).first<<"   "
   <<(*i).second<<endl;
 }
 cout<<"**************************************************"<<endl;
 //元素的删除erase(),clear()
 hm.erase("pear");
 for(i=hm.begin();i!=iend;i++)
 {
  cout<<(*i).first<<"   "
   <<(*i).second<<endl;
 }
 
 //元素的搜索
 j=hm.find("pear");
 i=hm.find("apple");
 cout<<"水果:"<<(*i).first<<"  "
  <<"价钱:"<<(*i).second<<endl;
 cout<<"**************************************************"<<endl;
 if(j!=hm.end())
 {
  cout<<"hash_map容器的个数"<<hm.size()<<endl;
 }
 else
 {
  cout<<"哈希表的表长:"<<hm.bucket_count()<<endl;
 }
 return 0;
}



展开阅读全文

没有更多推荐了,返回首页