STL---hash_map介绍与海量数据处理

一、hash_map简介

hash_map的用法和map是一样的,提供了 insert,size,count等操作,并且里面的元素也是以pair类型来存贮的。虽然对外部提供的函数和数据类型是一致的,但是其底层实现是完全不同的,map底层的数据结构是rb_tree,红黑树有序,每次操作的复杂度稳定在logN。而hansh_map却是哈希表(hashtable)来实现的,在hash函数恰当的情况下,可以提供更快的查询速度O(1),如果hash函数很烂,使用hash_map,将会是一个灾难,操作复杂度最坏将到O(N)。

例子:

int main()
{
    hash_map<int, string> hmap;//定义一个实例

    //插入
    hmap.insert(pair<int, string>(15, "tom"));//插入一个pair对象
    hmap.insert(hash_map<int,string>::value_type(24, "jerry"));//value_type就是pair类型的
    hmap[53]="simth";
    hmap[14]="john";
    hamp[-1]="henry";

    //定义迭代器并初始化
    hash_map<int, string>::iterator it=hmap.begin();

    while(it != hmap.end())
    {//遍历打印
        cout<<it->first<<" "<<it->second<<endl;
        it++;
    }
    it = hmap.find(23);//查找
    if(it!=hmap.end())
        PRINT(it);
    cout<<hmap.size()<<endl;
    cout<<hmap.count(58)<<endl;
    cout<<hmap.empty()<<endl;
    hash_map<int,string>::const_reverse_iterator cit = hmap.rend();
    PRINT(cit);
    return 0;
}

二、map与hash_map比较

总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据量大小无关,属于常数级别;map的查找速度是log(n)级别。hash还有hash函数的耗时。当有100w条记录的时候,map也只需要20次的比较,200w也只需要21次的比较!所以并不一定常数就比log(n) 小!

hash_map对空间的要求要比map高很多,所以是以空间换时间的方法,而且,hash_map如果hash函数和hash因子选择不好的话,也许不会达到你要的效果,所以至于用map,还是hash_map,从3个方面来权衡:查找速度, 数据量, 内存使用,还有一个就是你的经验!没有特别的标准。

另外可以通过重写 hash_compair仿函数,更改里面关于桶数量的定义,如果取值合适,也可以得到更优的性能。而且如果你的数据是自定义的类型,必须要重写这个仿函数。可以模仿原来的写法,所有的成员函数,成员变量一个不能少!

三、hash_map与hashtable比较

HashTable的应用非常广泛,HashMap是新框架中用来代替HashTable的类,也就是说建议使用HashMap,不要使用HashTable。可能你觉得HashTable很好用,为什么不用呢?这里简单分析他们的区别。

1.HashTable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap,这个区别就像Vector和ArrayList一样。

2.HashTable不允许null值(key和value都不可以),HashMap允许null值(key和value都可以)。

3.HashTable有一个contains(Object value),功能和containsValue(Object value)功能一样。

4.HashTable使用Enumeration,HashMap使用Iterator。

以上只是表面的不同,它们的实现也有很大的不同。

5.HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

6.哈希值的使用不同,HashTable直接使用对象的hashCode,代码是这样的:

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

而HashMap重新计算hash值,而且用与代替求模:

int hash = hash(k);
int i = indexFor(hash, table.length);

static int hash(Object x) {
  int h = x.hashCode();

  h += ~(h << 9);
  h ^= (h >>> 14);
  h += (h << 4);
  h ^= (h >>> 10);
  return h;
}
static int indexFor(int h, int length) {
  return h & (length-1);
}

以上只是一些比较突出的区别,当然他们的实现上还是有很多不同的。

四、为什么需要hash_map

map的查找速度是log2(n)级别,100万条记录,最多也只要20次的string.compare的比较,就能找到你要找的记录;200万条记录事,也只要用21次的比较。

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

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

五、海量数据处理

问题:

有一千万条短信,有重复,以文本文件的形式保存,一行一条,有重复。 请用5分钟时间,找出重复出现最多的前10条。

分析:
常规方法是先排序,在遍历一次,找出重复最多的前10条。但是排序的算法复杂度最低为nlgn。
可以设计一个hash_table, hash_map< string, int >,依次读取一千万条短信,加载到hash_table表中,并且统计重复的次数,与此同时维护一张最多10条的短信表。

这样遍历一次就能找出最多的前10条,算法复杂度为O(n)。

你可能感兴趣的:(海量数据)