zoukankan      html  css  js  c++  java
  • STL hash_map使用

    转载  STL hash_map使用 收藏

    http://blog.csdn.net/sdhongjun/archive/2009/09/03/4517325.aspx

    今天在使用STL中的hash_map模板遇到使用PTCHAR作为Key时无法对字符串进行正确比较的问题,在网上查找相应的文章可惜没有找到,但找到了http://www.stlchina.org/twiki/bin/view.pl/Main/STLDetailHashMaphttp://www.cppblog.com/guojingjia2006/archive/2008/01/12/41037.aspx两篇文章对解决我的问题帮了大忙,特将其内容贴出。

    zcl:在MFC中不建议此法,建议采用CMap, 参见 http://www.cnblogs.com/carl2380/archive/2011/01/03/1924568.html

    hash_map类在头文件hash_map中,和所有其它的C++标准库一样,头文件没有扩展名。如下声明:

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

         hash_map是一个聚合类,它继承自_Hash类,包括一个vector,一个list和一个pair,其中vector用于保存桶,list用于进行冲突处理,pair用于保存key->value结构,简要地伪码如下:

              class hash_map<class _Tkey, class _Tval>
              {
              private:
                   typedef pair<_Tkey, _Tval> hash_pair;
                   typedef list<hash_pair>    hash_list;
                   typedef vector<hash_list>  hash_table;
              };

         当然,这只是一个简单模型,C++标准库的泛型模版一向以嵌套复杂而闻名,初学时看类库,无疑天书啊。微软的hash_map类还聚合了hash_compare仿函数类,hash_compare类里又聚合了less仿函数类,乱七八糟的。

         下面说说使用方法:

         一、简单变量作为索引:整形、实性、指针型
         其实指针型也就是整形,算法一样。但是hash_map会对char*, const char*, wchar_t*, const wchar_t*做特殊处理。
         这种情况最简单,下面代码是整形示例:
                hash_map<int, int> IntHash;
                IntHash[1] = 123;
                IntHash[2] = 456;

                int val = IntHash[1];
                int val = IntHash[2];
         实型和指针型用法和整形一样,原理如下:
         1、使用简单类型作索引声明hash_map的时候,不需要声明模版的后两个参数(最后一个参数指名hash_map节点的存储方式,默认为pair,我觉得这就挺好,没必要修改),使用默认值就好。
         2、对于除过字符串的其它简单类型,hash_map使用模版函数 size_t hash_value(const _Kty& _Keyval) 计算hash值,计算方法是经典的掩码异或法,自动溢出得到索引hash值。微软的工程师也许开了一个玩笑,这个掩码被定义为0xdeadbeef(死牛肉,抑或是某个程序员的外号)。
         3、对于字符串指针作索引的时候,使用定类型函数inline size_t hash_value(const char *_Str)或inline size_t hash_value(const wchar_t *_Str)计算hash值,计算方法是取出每一个字符求和,自动溢出得到hash值。对于字符串型的hash索引,要注意需要自定义less仿函数。
         因为我们有理由认为,人们使用hash表进行快速查找的预期成本要比在hash表中插入的预期成本低得多,所以插入可以比查找昂贵些;基于这个假设,hash_map在有冲突时,插入链表是进行排序插入的,这样在进行查询冲突解决的时候就能够更快捷的找到需要的索引。
         但是,基于泛型编程的原则,hash_map也有理由认为每一种类型都支持使用"<"来判别两个类型值的大小,这种设计恰好让字符串类型无所适从,众所周知,两个字符串指针的大小并不代表字符串值的大小。见如下代码:
              hash_map<const char*, int> CharHash;
              CharHash["a"] = 123;
              CharHash["b"] = 456;

              char szInput[64] = "";
              scanf("%s", szInput);

              int val = CharHash[szInput];

         最终的结果就是无论输入任何字符串,都无法找到对应的整数值。因为输入的字符串指针是szInput指针,和"a"或"b"字符串常量指针的大小是绝对不会相同。解决方法如下:
         首先写一个仿函数CharLess,继承自仿函数基类binary_function(当然也可以不继承,这样写只是符合标准,而且写起来比较方便,不用被类似于指针的指针和指针的引用搞晕。

              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(stricmp(_Left, _Right) < 0 ? true : false);
                   }
              };

         很好,有了这个仿函数,就可以正确的使用字符串指针型hash_map了。如下:

              hash_map<const char*, int, hash_compare<const char*, CharLess> > CharHash;
              CharHash["a"] = 123;
              CharHash["b"] = 456;

              char szInput[64] = "";
              scanf("%s", szInput);

              int val = CharHash[szInput];
          
         现在就可以正常工作了。至此,简单类型的使用方法介绍完毕。

         二、用户自定义类型:比如对象类型,结构体。
         这种情况比价复杂,我们先说简单的,对于C++标准库的string类。
          
         庆幸的是,微软为basic_string(string类的基类)提供了hash方法,这使得使用string对象做索引简单了许多。值得注意(也值得郁闷)的是,虽然支持string的hash,string类却没有重载比较运算符,所以标准的hash_compare仿函数依旧无法工作。我们继续重写less仿函数。
              
              struct string_less : 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 : fase); 
                   } 
              };
                
         好了,我们可以书写如下代码:
                
              hash_map<string, int, hash_compare<string, string_less> > StringHash;
              StringHash["a"] = 123;
              StringHash["b"] = 456;

              string strKey = "a";

              int val = CharHash[strKey];
          
         这样就可以了。
          
         对于另外的一个常用的字符串类CString(我认为微软的CString比标准库的string设计要洒脱一些)更加复杂一些。很显然,标准库里不包含对于CString的支持,但CString却重载了比较运算符(郁闷)。我们必须重写hash_compare仿函数。值得一提的是,在Virtual Stdio 2003中,CString不再是MFC的成员,而成为ATL的成员,使用#include <atlstr.h>就可以使用。我没有采用重写hash_compare仿函数的策略,而仅仅是继承了它,在模版库中的继承是没有性能损耗的,而且能让我偷一点懒。 
         首先重写一个hash_value函数:
          
              inline size_t CString_hash_value(const CString& str) 
              { 
                   size_t value = _HASH_SEED; 
                   size_t size  = str.GetLength(); 
                   if (size > 0) { 
                        size_t temp = (size / 16) + 1; 
                        size -= temp; 
                        for (size_t idx = 0; idx <= size; idx += temp) { 
                             value += (size_t)str[(int)idx]; 
                        } 
                   } 
                   return(value); 
              }
          
         其次重写hash_compare仿函数:
          
              class CString_hash_compare : public hash_compare<CString> 
              { 
              public: 
                   size_t operator()(const CString& _Key) const 
                   { 
                        return((size_t)CString_hash_value(_Key));
                   }
       
                   bool operator()(const CString& _Keyval1, const CString& _Keyval2) const 
                   { 
                        return (comp(_Keyval1, _Keyval2)); 
                   } 
              };
                
         
    上面的重载忽略了基类对于less仿函数的引入,因为CString具备比较运算符,我们可以使用默认的less仿函数,在这里映射为comp。好了,我们可以声明新的hash_map对象如下:

              hash_map<CString, int, CString_hash_compare> CStringHash;

         其余的操作一样一样的。

         zcl 备注:上面标绿的文字应该是如下写法

    class CString_hash_compare// : //public hash_compare<CString> 

    public:
     inline size_t CString_hash_value( const CString& str) const
     { 
        size_t value = _HASH_SEED; 
        size_t size  = str.GetLength(); 
        if (size > 0) { 
       size_t temp = (size / 16) + 1; 
       size -= temp; 
       for (size_t idx = 0; idx <= size; idx += temp) { 
         value += (size_t)str[(int)idx]; 
       } 
        } 
        return(value); 
     }
    public: 
     size_t operator()(const CString& _Key) const 
     { 
      return((size_t)CString_hash_value(_Key));
     }

     bool operator()( CString& _Keyval1,  CString& _Keyval2)  
     { 
      return (_Keyval1.Compare(_Keyval2)==0); 
     } 
    };

    备注完毕

         下来就说说对于自定义对象的使用方法:首先定义
          
              struct IHashable 
              { 
                   virtual unsigned long hash_value() const = 0; 
                   virtual bool operator < (const IHashable& val) const = 0; 
                   virtual IHashable& operator = (const IHashable& val) = 0; 
              };
          
         让我们自写的类都派生自这里,有一个标准,接下来定义我们的类:
          
              class CTest : public IHashable 
              { 
              public: 
                   int m_value; 
                   CString m_message; 
              public: 
                   CTest() : m_value(0) 
                   { 
                   }
                
                   CTest(const CTest& obj) 
                   { 
                        m_value = obj.m_value; 
                        m_message = obj.m_message; 
                   } 
              public: 
                   virtual IHashable& operator = (const IHashable& val) 
                   { 
                        m_value   = ((CTest&)val).m_value; 
                        m_message = ((CTest&)val).m_message; 
                        return(*this); 
                   }
                
                   virtual unsigned long hash_value() const
                   {
                        // 这里使用类中的m_value域计算hash值,也可以使用更复杂的函数计算所有域总的hash值
                        return(m_value ^ 0xdeadbeef  
                   }
                
                   virtual bool operator < (const IHashable& val) const 
                   { 
                        return(m_value < ((CTest&)val).m_value); 
                   } 
              };
          
         用这个类的对象做为hash索引准备工作如下,因为接口中规定了比较运算符,所以这里可以使用标准的less仿函数,所以这里忽略:
          
              template<class _Tkey> 
              class MyHashCompare : public hash_compare<_Tkey> 
              { 
              public: 
                   size_t operator()(const _Tkey& _Key) const 
                   { 
                        return(_Key.hash_value()); 
                   }
                
                   bool operator()(const _Tkey& _Keyval1, const _Tkey& _Keyval2) const 
                   { 
                        return (comp(_Keyval1, _Keyval2)); 
                   } 
              };
                
         下来就这样写:
          
              CTest test; 
              test.m_value = 123; 
              test.m_message = "This is a test";
          
              MyHash[test] = 2005;
                
              int val = MyHash[test];
          
         可以看到正确的数字被返回。
          
         三、关于hash_map的思考:
          
         1、性能分析:采用了内联代码和模版技术的hash_map在效率上应该是非常优秀的,但我们还需要注意如下几点:
          
         * 经过查看代码,字符串索引会比简单类型索引速度慢,自定义类型索引的性能则和我们选择hash的内容有很大关系,简单为主,这是使用hash_map的基本原则。 
         * 可以通过重写hash_compair仿函数,更改里面关于桶数量的定义,如果取值合适,也可以得到更优的性能。如果桶数量大于10,则牢记它应该是一个质数。 
         * 在自定义类型是,重载的等号(或者拷贝构造)有可能成为性能瓶颈,使用对象指针最为索引将是一个好的想法,但这就必须重写less仿函数,理由同使用字符串指针作为索引。

    自己使用上面的方法成功解决了使用PTCHAR作为Key的使用,其解决方法如下:

    inline size_t PTCHAR_hash_value(const PTCHAR str)
    {
     size_t value = _HASH_SEED;
     size_t size = _tcslen(str);
     if (size > 0)
     {
      size_t temp = (size/16) + 1;
      size -= temp;
      for (size_t idx=0; idx<=size; idx+=temp)
      {
       value += (size_t)str[(int)idx];
      }
     }

     return value;
    }

    class PTCHAR_hash_compare : public stdext::hash_compare<PTCHAR>
    {
    public:
     size_t operator()(const PTCHAR _Key) const
     {
      return ((size_t)PTCHAR_hash_value(_Key));
     }

     bool operator()(const PTCHAR _Keyval1, const PTCHAR _Keyval2) const
     {
      return (_tcscmp(_Keyval1, _Keyval2));
     }
    };

    stdext::hash_map<PTCHAR, long, PTCHAR_hash_compare > myHash;


    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sdhongjun/archive/2009/09/03/4517325.aspx

  • 相关阅读:
    Redis持久化
    Redis进阶——事务、TTL、排序、消息通知、管道
    行为设计模式
    Redis数据类型
    ASP.NET并发处理
    c# 泛型有什么作用?
    IBatisNet 升级到 .Net Framework 4.0 时发现 IBatisNet 一小BUG
    【Python3】用for循环和while循环分别打印出如下格式“九九乘法表”
    Visual Studio 2010 SP1 WPF程序属性重命名BUG,小心!
    为什么用SOA架构?
  • 原文地址:https://www.cnblogs.com/carl2380/p/2086306.html
Copyright © 2011-2022 走看看