STL容器之map与hash_map

一、简介

就应用来说,map已经是STL标准库的成员,而hash_map暂时还未进入标准库,是扩展ext中的一个功能,但也是非常常用并且非常重要的库。

二、简单对比

首先,要说的是这两种数据结构的都提供了KEY-VALUE的存储和查找的功能。但是实现是不一样的,map是用的红黑树,查询时间复杂度为log(n)。而hash_map是用的哈希表,查询时间复杂度理论上可以是常数,但是消耗内存大,是一种以存储换时间的方法。

树查找,在总查找效率上比不上hash表,但是它很稳定,它的算法复杂度不会出现波动。在一次查找中,你可以断定它最坏的情况下其复杂度不会超过O(log2N)。而hash表就不一样,是O(1),还是O(N),或者在其之间,你并不能把握。

三、hash_map的使用

可见,如果定义完整的hash_map,需要提供<key类型,value类型,哈希函数,key相等判断函数,value类型内存分配器>等5个模板参数,由于后三个都有默认值,所以一般我们只需要提供前两个。

1> 定义__gnu_cxx::hash_map<string, int> myHashMap不会出错,然而一旦对myHashMap进行操作,就会出现编译错误“instantiated from here”,这是因为gnu版本的hash_map只实现了有限的几个hash模板函数(见第三个模板参数,这些函数在hash_fun.h中),而这些函数里包括hash<const char*>,但是不包括hash<std::string>的实例化。解决办法是定义哈希表前自己特化一个实例,这样编译器就知道调用这个函数了。

namespace __gnu_cxx
{
        template <>
        struct hash<string>
        {
                size_t operator()(const string &s) const
                { return __stl_hash_string(s.c_str()); }
        };
}

2> 第四个参数key相等判断函数的意义

int main()
{
        __gnu_cxx::hash_map<const char *, int> myHashMap;
        char name1[10] = "zhu";
        char name2[10] = "zhu";
        myHashMap[name1] = 1;
        __gnu_cxx::hash_map<const char *, int>::iterator it = myHashMap.find(name2);
        if (it == myHashMap.end())
                cout << "not find" << endl;
        return 0;
}

你会发现,虽然name1和name2都是zhu,但是插入了name1,用name2去查找时,还是查无结果。这是涉及到第四个模板参数,判断key相等,默认的是std::equal_to,而这个函数的定义是用operator==来进行判断的,指针的相等当然就是地址一样了,而name1和name2的地址显然不同。解决办法是用自己指定的函数模板替代默认的。

#include <cstring>
#include <iostream>
#include <ext/hash_map>
#include <backward/hash_fun.h>

using namespace std;

template <class _Tp>
struct my_equal_to : public binary_function<_Tp, _Tp, bool>
{
    bool operator()(const _Tp& __x, const _Tp& __y) const
    { return strcmp(__x, __y) == 0; }
};

int main()
{
        __gnu_cxx::hash_map<const char *, int, __gnu_cxx::hash<const char*>, my_equal_to<const char*> > myHashMap;
        char name1[10] = "zhu";
        char name2[10] = "zhu";
        myHashMap[name1] = 1;
        __gnu_cxx::hash_map<const char *, int, __gnu_cxx::hash<const char*>, my_equal_to<const char*> >::iterator it = myHashMap.find(name2);
        if (it == myHashMap.end())
                cout << "not find" << endl;
        else
                cout << it->second << endl;
        return 0;
}

用刚刚特化的hash_map<string, int>就是可以找到的,因为string重载了operator==操作符。

编译使用-Wno-deprecated选项,不然会有backward_warning.h头文件里的告警。

四、肤浅的对比测试(map,系统hash函数的hash_map及自写hash函数的hash_map)

[[email protected] ~]$ cat /tmp/name.txt | wc -l
25848136
#从现有的游戏数据库里拉了561916个角色名(里面本来就有重复的),然后重复追加了几次,变成了
#2584万行的数据


1.系统hash函数的hash_map实现

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

using namespace std;
//特化hash函数的string版本
namespace __gnu_cxx
{
        template <>
        struct hash<string>
        {
                size_t operator()(const string &s) const
                { return __stl_hash_string(s.c_str()); }
        };
}
//计算当前时间
void curTime()
{
        time_t aTime = time(NULL);
        struct tm * curtime = localtime(&aTime);
        char ctemp[20];
        strftime(ctemp, 20, "%Y-%m-%d %H:%M:%S" , curtime);
        cout<<ctemp<<endl;
}
int main()
{
        __gnu_cxx::hash_map<string, int> fileMap;
        string temp;        //存放每行的临时字符串
        int i = 0;          //统计总个数
        ifstream in;
        in.open("/tmp/name.txt", ifstream::in);
        if (!in)
        {
                cout << "open file failed" << endl;
                return 1;
        }
        curTime();        //从这里开始计时
        while (in >> temp)
        {
                if (fileMap.find(temp) == fileMap.end())
                {
                        ++i;
                        fileMap[temp] = i;
                }
        }
        curTime();        //计时结束
        cout << i << endl;
        in.close();
        return 0;
}
#编译
[[email protected] ~]$ g++ -Wno-deprecated 3.cpp -o hashMap


2.自写hash函数的hash_map

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

using namespace std;

struct strHash{
        size_t operator()(const string& str) const
        {
                unsigned long __h = 0;
                for (size_t i = 0 ; i < str.size() ; i ++)
                         __h = 107*__h + str[i];
                return size_t(__h);
        }
};

void curTime()
{
        time_t aTime = time(NULL);
        struct tm * curtime = localtime(&aTime);
        char ctemp[20];
        strftime(ctemp, 20, "%Y-%m-%d %H:%M:%S" , curtime);
        cout<<ctemp<<endl;
}

int main()
{
        __gnu_cxx::hash_map<string, int, strHash> fileMap;
        string temp;
        int i = 0;
        ifstream in;
        in.open("/tmp/name.txt", ifstream::in);
        if (!in)
        {
                cout << "open file failed" << endl;
                return 1;
        }
        curTime();
        while (in >> temp)
        {
                if (fileMap.find(temp) == fileMap.end())
                {
                        ++i;
                        fileMap[temp] = i;
                }
        }
        curTime();
        cout << i << endl;
        in.close();
        return 0;
}
#编译
[[email protected] ~]$ g++ -Wno-deprecated 4.cpp -o strhashMap


3.STL的map

#include <iostream>
#include <fstream>
#include <string>
#include <map>

using namespace std;

void curTime()
{
        time_t aTime = time(NULL);
        struct tm * curtime = localtime (&aTime);
        char ctemp[20];
        strftime(ctemp, 20, "%Y-%m-%d %H:%M:%S" , curtime);
        cout<<ctemp<<endl;
}

int main()
{
        map<string, int> fileMap;
        string temp;
        int i = 0;
        ifstream in;
        in.open("/tmp/name.txt", ifstream::in);
        if (!in)
        {
                cout << "open file failed" << endl;
                return 1;
        }
        curTime();
        while (in >> temp)
        {
                if (fileMap.find(temp) == fileMap.end())
                {
                        ++i;
                        fileMap[temp] = i;
                }
        }
        curTime();
        cout << i << endl;
        in.close();
        return 0;
}
#编译
[[email protected] ~]$ g++ 2.cpp -o map

4.执行查看结果

[[email protected] ~]$ ./hashMap       #7秒
2015-11-06 16:25:41
2015-11-06 16:25:48
459256
[[email protected] ~]$ ./strhashMap    #8秒,和上面的相差无几
2015-11-06 16:25:50
2015-11-06 16:25:58
459256
[[email protected] ~]$ ./map           #26秒
2015-11-06 16:26:02
2015-11-06 16:26:28
459256


五、总结

这个测试仅仅是个人娱乐,并没有什么实际价值。最后就是一句话,hash_map是基于hash_table实现的,而hash_table是把以双刃剑,用的好效率很高O(1),用的不好奔着O(N)就去了。

六、注意hash_map死循环

这个问题简单说来,就是gnu的实现是,内部有个_M_Cur指针指示当前位置A,每次计算operator++,都用当前位置的key调用hash函数计算下一个位置B,如果key传入hash_map以后,又在外部将其内容破坏,导致hash函数计算后的B位置在A位置之前,那么从B到达A以后,又会跳回B,形成B-A区间的死循环。

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

using namespace std;
int main()
{
        __gnu_cxx::hash_map<char *, int> hashMap;
        char name[10] = "zhu";
        hashMap.insert(pair<char *, int>(name, 1));
        strncpy(name, "wang", 10);      //在外部改变了已经传入hash_map中的key,导致死循环
        for (__gnu_cxx::hash_map<char *, int>::iterator it = hashMap.begin(); it != hashMap.end(); ++it)
        {
                cout << it->first << "  " << it->second << endl;
        }
        return 0;
}
时间: 2024-08-26 21:36:24

STL容器之map与hash_map的相关文章

STL容器之map

Map是一个关联容器,它内部有两个数据,第一个(first)称为关键字(key),第二个(second)称为关键字的值(value),key与value二者是一一对应的(称为pair),且key在map中关键字是唯一的.map内部自建一颗严格意义上的平衡二叉树,对数据有排序功能,因此,map内部数据都是有排序的(less或greater). map.insert() ? 实际上,map的insert比较简单,因此不论你怎么插,其内部的平衡二叉树都会根据关键字key自动排序.在上述代码中可知,ke

初探STL容器之Vector

vector 特点: 1.可变长的动态数组 2.使用时包含头文件 #include <vector> 3.支持随机访问迭代器 ? 根据下标随机访问某个元素时间为常数 ? 在尾部添加速度很快 ? 在中间插入慢 成员函数 初始化 vector(); 初始化成空 vector(int n); 初始化成有n个元素 vector(int n, const T & val); 初始化成有n个元素, 每个元素的值都是val,类型是T vector(iterator first, iterator l

初探STL容器之List

List 特点: 1.实质上是双向链表 2.使用时包含<list>头文件 #include<list> 3.不支持随机访问迭代器,只能使用双向迭代器  //因此不能使用一些算法和运算符操作 4.在任何位置的插入.删除操作都是常数时间 成员函数 初始化 list <int> intlist0; // 创建空的 intlist list <int> intlist1( 3 ); //包含3个元素 list <int> intlist2( 5, 2 )

STL 中的map 与 hash_map的理解

可以参考侯捷编著的<STL源码剖析> STL 中的map 与 hash_map的理解 1.STL的map底层是用红黑树存储的,查找时间复杂度是log(n)级别: 2.STL的hash_map底层是用hash表存储的,查询时间复杂度是常数级别: 3.什么时候用map,什么时候用hash_map? 这个要看具体的应用,不一定常数级别的hash_map一定比log(n)级别的map要好,hash_map的hash函数以及解决地址冲突等都要耗时,而且众所周知hash表是以空间效率来换时间效率的,因而h

关于STL中的map和hash_map

以下全部copy于:http://blog.chinaunix.net/uid-26548237-id-3800125.html 在网上看到有关STL中hash_map的文章,以及一些其他关于STL map和hash_map的资料,总结笔记如下:     1.STL的map底层是用红黑树实现的,查找时间复杂度是log(n):     2.STL的hash_map底层是用hash表存储的,查询时间复杂度是O(1):     3.什么时候用map,什么时候用hash_map?     这个药看具体的

Java容器之Map接口

Map 接口: 1. 实现 Map 接口的类是用来存储键-值(key-value)对: 2. Map 接口的实现类有 HashMap 和 TreeMap 等: 3. Map 类中存储的键-值对,通过键来标识,所以键值不能重复. 4. Map 接口的常用方法如下:    4.1 object put(K key, V value):将指定的值与此映射中的指定键关联(可选操作); 4.2 object get(Object key):返回指定键所映射的值: 4.3 object remove(Obj

C++关联容器之map

1.map简介 map中的元素是关键字-值对:关键字起到索引的作用,值表示与索引相关的数据.我们常用的字典就是很好的map的实例,单词作为索引,其中文含义代表其值.map类型通常被称为关联数组,其和数组很相似,只不过其下标不是整数而是关键 字,我们通过关键字来查找值而不是位置.比如电话簿也是一个map的例子,姓名作为关键字其对应的值就为该人的电话号码.map类型定义在头文件map中. 注意:map是有序的且不允许重复关键字的关联容器!其有序的实现是依靠关键字类型中的"<"来实现的

C++ 关联容器之map插入相同键元素与查找元素操作

一.插入相同键元素操作 (1)insert方法 在map中的键必须是唯一的,当想map中连续插入键相同但值不同的元素时,编译和运行时都不会发生任何错误,系统会忽略后面的对已存在的键的插入操作,如 1 map<int,int> m1; 2 m1.insert(make_pair(1,2)); 3 m1.insert(make_pair(1,3)); 4 for(map<int,int>::iterator mit=m1.begin();mit!=m1.end();mit++){ 5

c++STL容器之list容器

链表:将数据进行链式存储.物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接进行实现的. STL中的链表是一个双向循环链表. 一.构造函数 list<T> lst; list(bag,end); list(n,elem); list(const list &list); 二.list的赋值和交换 assign(beg,end); assign(n,ele); list& operator=(const list &list); swap(list)