C++ hashmap

轉載自http://blog.csdn.net/sdhongjun/article/details/4517325


今天在使用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兩篇文章對解決我的問題幫了大忙,特將其內容貼出。

 

hash_map類在頭文件hash_map中,和所有其它的C++標準庫一樣,頭文件沒有擴展名。如下聲明:

  

  1. #include <hash_map>  
  2. using namespace std;  
  3. using namespace stdext;  

     hash_map是一個聚合類,它繼承自_Hash類,包括一個vector,一個list和一個pair,其中vector用於保存桶,list用於進行衝突處理,pair用於保存key->value結構,簡要地僞碼如下:

  1.  class hash_map<class _Tkey, class _Tval>  
  2. {  
  3. private:  
  4.     typedef pair<_Tkey, _Tval> hash_pair;  
  5.     typedef list<hash_pair>    hash_list;  
  6.     typedef vector<hash_list>  hash_table;  
  7. };  


     當然,這只是一個簡單模型,C++標準庫的泛型模版一向以嵌套複雜而聞名,初學時看類庫,無疑天書啊。微軟的hash_map類還聚合了hash_compare仿函數類,hash_compare類裏又聚合了less仿函數類,亂七八糟的。

     下面說說使用方法:

     一、簡單變量作爲索引:整形、實性、指針型
     其實指針型也就是整形,算法一樣。但是hash_map會對char*, const char*, wchar_t*, const wchar_t*做特殊處理。
     這種情況最簡單,下面代碼是整形示例:
 

  1. hash_map<intint> IntHash;  
  2. IntHash[1] = 123;  
  3. IntHash[2] = 456;  
  4. int val = IntHash[1];  
  5. 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也有理由認爲每一種類型都支持使用"<"來判別兩個類型值的大小,這種設計恰好讓字符串類型無所適從,衆所周知,兩個字符串指針的大小並不代表字符串值的大小。見如下代碼:

  1. hash_map<const char*, int> CharHash;  
  2. CharHash["a"] = 123;  
  3. CharHash["b"] = 456;  
  4. char szInput[64] = "";  
  5. scanf("%s", szInput);  
  6. int val = CharHash[szInput];  

     最終的結果就是無論輸入任何字符串,都無法找到對應的整數值。因爲輸入的字符串指針是szInput指針,和"a"或"b"字符串常量指針的大小是絕對不會相同。解決方法如下:
     首先寫一個仿函數CharLess,繼承自仿函數基類binary_function(當然也可以不繼承,這樣寫只是符合標準,而且寫起來比較方便,不用被類似於指針的指針和指針的引用搞暈。

          

  1. struct CharLess : public binary_function<const char*, const char*, bool>  
  2. {  
  3. public:  
  4.     result_type operator()(const first_argument_type& _Left, const second_argument_type& _Right) const  
  5.     {  
  6.         return(stricmp(_Left, _Right) < 0 ? true : false);  
  7.     }  
  8. };  

     很好,有了這個仿函數,就可以正確的使用字符串指針型hash_map了。如下:

         

  1. hash_map<const char*, int, hash_compare<const char*, CharLess> > CharHash;  
  2. CharHash["a"] = 123;  
  3. CharHash["b"] = 456;  
  4. char szInput[64] = "";  
  5. scanf("%s", szInput);  
  6. int val = CharHash[szInput];  

      
     現在就可以正常工作了。至此,簡單類型的使用方法介紹完畢。

     二、用戶自定義類型:比如對象類型,結構體。
     這種情況比價複雜,我們先說簡單的,對於C++標準庫的string類。
      
     慶幸的是,微軟爲basic_string(string類的基類)提供了hash方法,這使得使用string對象做索引簡單了許多。值得注意(也值得鬱悶)的是,雖然支持string的hash,string類卻沒有重載比較運算符,所以標準的hash_compare仿函數依舊無法工作。我們繼續重寫less仿函數。
          
         

  1.  struct string_less : public binary_function<const string, const string, bool>  
  2. {   
  3. public:   
  4.     result_type operator()(const first_argument_type& _Left, const second_argument_type& _Right) const   
  5.     {   
  6.         return(_Left.compare(_Right) < 0 ? true : fase);   
  7.     }   
  8. };  
            
     好了,我們可以書寫如下代碼:
            
          
  1. hash_map<string, int, hash_compare<string, string_less> > StringHash;  
  2. StringHash["a"] = 123;  
  3. StringHash["b"] = 456;  
  4. string strKey = "a";  
  5. int val = CharHash[strKey];  

      
     這樣就可以了。
      
     對於另外的一個常用的字符串類CString(我認爲微軟的CString比標準庫的string設計要灑脫一些)更加複雜一些。很顯然,標準庫裏不包含對於CString的支持,但CString卻重載了比較運算符(鬱悶)。我們必須重寫hash_compare仿函數。值得一提的是,在Virtual Stdio 2003中,CString不再是MFC的成員,而成爲ATL的成員,使用#include <atlstr.h>就可以使用。我沒有采用重寫hash_compare仿函數的策略,而僅僅是繼承了它,在模版庫中的繼承是沒有性能損耗的,而且能讓我偷一點懶。
     首先重寫一個hash_value函數:
      
        

  1. inline size_t CString_hash_value(const CString& str)   
  2. {   
  3.     size_t value = _HASH_SEED;   
  4.     size_t size  = str.GetLength();   
  5.     if (size > 0) {   
  6.         size_t temp = (size / 16) + 1;   
  7.         size -= temp;   
  8.         for (size_t idx = 0; idx <= size; idx += temp) {   
  9.             value += (size_t)str[(int)idx];   
  10.         }   
  11.     }   
  12.     return(value);   
  13. }  


      
     其次重寫hash_compare仿函數:
      
         
  1. class CString_hash_compare : public hash_compare<CString>   
  2. {   
  3. public:   
  4.     size_t operator()(const CString& _Key) const   
  5.     {   
  6.         return((size_t)CString_hash_value(_Key));  
  7.     }  
  8.      
  9.     bool operator()(const CString& _Keyval1, const CString& _Keyval2) const   
  10.     {   
  11.         return (comp(_Keyval1, _Keyval2));   
  12.     }   
  13. };  


            
     上面的重載忽略了基類對於less仿函數的引入,因爲CString具備比較運算符,我們可以使用默認的less仿函數,在這裏映射爲comp。好了,我們可以聲明新的hash_map對象如下:

          

  1. hash_map<CString, int, CString_hash_compare> CStringHash;  

     其餘的操作一樣一樣的。

     下來就說說對於自定義對象的使用方法:首先定義
      
         

  1. struct IHashable   
  2.    
  3.    virtual unsigned long hash_value() const = 0;   
  4.    virtual bool operator < (const IHashable& val) const = 0;   
  5.    virtual IHashable& operator = (const IHashable& val) = 0;   
  6. ;  


      
     讓我們自寫的類都派生自這裏,有一個標準,接下來定義我們的類:
      
        
  1. class CTest : public IHashable   
  2. {   
  3. public:   
  4.     int m_value;   
  5.     CString m_message;   
  6. public:   
  7.     CTest() : m_value(0) {}  
  8.               
  9.     CTest(const CTest& obj)   
  10.     {   
  11.         m_value = obj.m_value;   
  12.         m_message = obj.m_message;   
  13.     }   
  14. public:   
  15.     virtual IHashable& operator = (const IHashable& val) {   
  16.         m_value   = ((CTest&)val).m_value;   
  17.         m_message = ((CTest&)val).m_message;   
  18.         return(*this);   
  19.     }  
  20.               
  21.     virtual unsigned long hash_value() const {  
  22.         // 這裏使用類中的m_value域計算hash值,也可以使用更復雜的函數計算所有域總的hash值  
  23.         return(m_value ^ 0xdeadbeef    
  24.     }  
  25.               
  26.     virtual bool operator < (const IHashable& val) const {   
  27.         return(m_value < ((CTest&)val).m_value);   
  28.     }   
  29. };  

      
     用這個類的對象做爲hash索引準備工作如下,因爲接口中規定了比較運算符,所以這裏可以使用標準的less仿函數,所以這裏忽略:
      
         
  1. template<class _Tkey>   
  2. class MyHashCompare : public hash_compare<_Tkey>   
  3. {   
  4. public:   
  5.     size_t operator()(const _Tkey& _Key) const {   
  6.         return(_Key.hash_value());   
  7.     }  
  8.   
  9.   
  10.     bool operator()(const _Tkey& _Keyval1, const _Tkey& _Keyval2) const {   
  11.         return (comp(_Keyval1, _Keyval2));   
  12.     }   
  13. };  
  14.               


     下來就這樣寫:
     
  1. CTest test;   
  2. test.m_value = 123;   
  3. test.m_message = "This is a test";  
  4.   
  5.   
  6. MyHash[test] = 2005;  
  7.   
  8.   
  9. int val = MyHash[test];  

      
     可以看到正確的數字被返回。
      
     三、關於hash_map的思考:
      
     1、性能分析:採用了內聯代碼和模版技術的hash_map在效率上應該是非常優秀的,但我們還需要注意如下幾點:
      
     * 經過查看代碼,字符串索引會比簡單類型索引速度慢,自定義類型索引的性能則和我們選擇hash的內容有很大關係,簡單爲主,這是使用hash_map的基本原則。
     * 可以通過重寫hash_compair仿函數,更改裏面關於桶數量的定義,如果取值合適,也可以得到更優的性能。如果桶數量大於10,則牢記它應該是一個質數。
     * 在自定義類型是,重載的等號(或者拷貝構造)有可能成爲性能瓶頸,使用對象指針最爲索引將是一個好的想法,但這就必須重寫less仿函數,理由同使用字符串指針作爲索引。

 

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

 

  1. inline size_t PTCHAR_hash_value(const PTCHAR str)  
  2. {  
  3.     size_t value = _HASH_SEED;  
  4.     size_t size = _tcslen(str);  
  5.     if (size > 0) {  
  6.         size_t temp = (size/16) + 1;  
  7.         size -= temp;  
  8.         for (size_t idx=0; idx<=size; idx+=temp) {  
  9.             value += (size_t)str[(int)idx];  
  10.         }  
  11.     }  
  12.     return value;  
  13. }  
  14. class PTCHAR_hash_compare : public stdext::hash_compare<PTCHAR>  
  15. {  
  16. public:  
  17.     size_t operator()(const PTCHAR _Key) const {  
  18.         return ((size_t)PTCHAR_hash_value(_Key));  
  19.     }  
  20.     bool operator()(const PTCHAR _Keyval1, const PTCHAR _Keyval2) const {  
  21.         return (_tcscmp(_Keyval1, _Keyval2));  
  22.     }  
  23. };  
  24. stdext::hash_map<PTCHARlong, PTCHAR_hash_compare > myHash;  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章