1. 程式人生 > >map/unordered_map原理和使用整理

map/unordered_map原理和使用整理

1.結論

新版的hash_map都是unordered_map了,這裡只說unordered_map和map.

執行效率方面:unordered_map最高,而map效率較低但 提供了穩定效率和有序的序列。

佔用記憶體方面:map記憶體佔用略低,unordered_map記憶體佔用略高,而且是線性成比例的。

需要無序容器,快速查詢刪除,不擔心略高的記憶體時用unordered_map;有序容器穩定查詢刪除效率,記憶體很在意時候用map

2.原理

map的內部實現是二叉平衡樹(紅黑樹);hash_map內部是一個hash_table一般是由一個大vector,vector元素節點可掛接連結串列來解決衝突,來實現.

hash_map其插入過程是:
  1. 得到key
  2. 通過hash函式得到hash值
  3. 得到桶號(一般都為hash值對桶數求模)
  4. 存放key和value在桶內。
其取值過程是:
  1. 得到key
  2. 通過hash函式得到hash值
  3. 得到桶號(一般都為hash值對桶數求模)
  4. 比較桶的內部元素是否與key相等,若都不相等,則沒有找到。
  5. 取出相等的記錄的value。
hash_map中直接地址用hash函式生成,解決衝突,用比較函式解決。

3.記憶體佔用測試

測試程式碼: 測試條件window下,VS2015 C++。string為key, int 為value。
1.UnorderMap:
#include <unordered_map>  
#include <string>  
#include <iostream>
#include <windows.h>
#include <psapi.h>  
#pragma comment(lib,"psapi.lib") 
using namespace std;
using namespace stdext;
void showMemoryInfo(void)
{
	HANDLE handle = GetCurrentProcess();
	PROCESS_MEMORY_COUNTERS pmc;
	GetProcessMemoryInfo(handle, &pmc, sizeof(pmc));
	cout << "Memory Use:" << pmc.WorkingSetSize/1024.0f << "KB/" << pmc.PeakWorkingSetSize/1024.0f << "KB, Virtual Memory Use:" << pmc.PagefileUsage/1024.0f << "KB/" << pmc.PeakPagefileUsage/1024.0f << "KB" << endl;
}

//define the class  
/*-------------------------------------------*/
/*函式類
*作為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;
	}
};

struct CharLess : 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 : false);
	}
};

int main()
{
	
	cout << "Test HashMap(unorder map) Memory Use  Start..."<< endl;
	// VC下自定義型別  
	unordered_map<string, int, hash_compare<string, CharLess> > CharHash;
	for (int i = 0; i < 10000000; i++)
	{
		string key = to_string(i);
		CharHash[key] = i;
	}
	cout << "Test HashMap(unorder map) Memory Use  End." << endl;
	showMemoryInfo();
	while (1);
	return 0;
}


2.map:
#include <iostream>
#include <map>
#include <string>
#include <windows.h>
#include <psapi.h>  
#pragma comment(lib,"psapi.lib") 
using namespace std;
void showMemoryInfo(void)
{
	HANDLE handle = GetCurrentProcess();
	PROCESS_MEMORY_COUNTERS pmc;
	GetProcessMemoryInfo(handle, &pmc, sizeof(pmc));
	cout << "Memory Use:" << pmc.WorkingSetSize / 1024.0f << "KB/" << pmc.PeakWorkingSetSize / 1024.0f << "KB, Virtual Memory Use:" << pmc.PagefileUsage / 1024.0f << "KB/" << pmc.PeakPagefileUsage / 1024.0f << "KB" << endl;
}


int main()
{
	cout << "Test Map(Red-Black Tree) Memory Use  Start..." << endl;
	// VC下自定義型別  
	//map<const char*, int, hash_compare<const char*, CharLess> > CharHash;
	map<string, int> CharMap;
	for (int i = 0; i < 10000000; i++)
	{
		string key = to_string(i);
		CharMap[key] = i;
	}
	cout << "Test Map(Red-Black Tree) Memory Use  End." << endl;
	showMemoryInfo();
	while (1);
	return 0;
}



測試結果: 1000個元素: map:
unorder_map:
10萬個元素: map:
unorder_map:
1000萬個元素: map:
unorder_map:
可以看到unordermap始終比map記憶體空間佔用量大些,而且是線性成比例的。

4.效能特點

非頻繁的查詢用map比較穩定;頻繁的查詢用hash_map效率會高一些,c++11中的unordered_map查詢效率會更高一些但是記憶體佔用比hash_map稍微大點。unordered_map 就是 boost 裡面的 hash_map 實現。

其實,stl::map對於與java中的TreeMap,而boost::unordered_map對應於java中的HashMap。
python中的map就是hashmap實現的,所以查詢效率會比C++的map查詢快。(java,python官方版的虛擬機器都是用C語言實現的,所以內部的思想和方法都是通用的。)

若考慮有序,查詢速度穩定,容器元素量少於1000,非頻繁查詢那麼考慮使用map。
若非常高頻查詢(100個元素以上,unordered_map都會比map快),內部元素可非有序,資料大超過1k甚至幾十萬上百萬時候就要考慮使用unordered_map
(元素上千萬上億時4GB的記憶體就要擔心記憶體不足了,需要資料庫儲存過程挪動到磁碟中)。
hash_map相比unordered_map就是千萬級別以上記憶體佔用少15MB,上億時候記憶體佔用少300MB,百萬以下都是unordered_map佔用記憶體少,
且unordered_map插入刪除相比hash_map都快一倍,查詢效率相比hash_map差不多,或者只快了一點約1/50到1/100。
綜合非有序或者要求穩定用map,都應該使用unordered_map,set型別也是類似的。
unordered_map 查詢效率快五倍,插入更快,節省一定記憶體。如果沒有必要排序的話,儘量使用 hash_map(unordered_map 就是 boost 裡面的 hash_map 實現)

5.使用unordered_map

unordered_map需要過載hash_value函式,並重載operator ==運算子。
詳細參考見(感謝寫的這麼好的文章):

6.使用Hash_map需要注意的問題

/**
 *\author peakflys
 *\brief 演示hash_map鍵值更改造成的問題
 
*/
#include <iostream>
#include <ext/hash_map>
struct Unit
{
    char name[32];
    unsigned int score;
    Unit(const char *_name,const unsigned int _score) : score(_score)
    {   
        strncpy(name,_name,32);
    }   
};
int main()
{
    typedef __gnu_cxx::hash_map<char*,Unit*> uHMap;
    typedef uHMap::value_type hmType;
    typedef uHMap::iterator hmIter;
    uHMap hMap;
    Unit *unit1 = new Unit("peak",100);
    Unit *unit2 = new Unit("Joey",20);
    Unit *unit3 = new Unit("Rachel",40);
    Unit *unit4 = new Unit("Monica",90);
    hMap[unit1->name] = unit1;
    hMap[unit2->name] = unit2;
    hMap.insert(hmType(unit3->name,unit3));
    hMap.insert(hmType(unit4->name,unit4));
    for(hmIter it=hMap.begin();it!=hMap.end();++it)
    {   
        std::cout<<it->first<<"\t"<<it->second->score<<std::endl;//正常操作    }   
    for(hmIter it=hMap.begin();it!=hMap.end();++it)
   {
        Unit *unit = it->second;
        //hMap.erase(it++);
        delete unit; //delete釋放節點記憶體,但是hMap沒有除去,造成hMap內部錯亂,有可能宕機    } 
     hmIter it = hMap.begin();
    strncpy(it->first,"cc",32);//強行更改    for(hmIter it=hMap.begin();it!=hMap.end();++it)
    {   
        std::cout<<it->first<<"\t"<<it->second->score<<std::endl;//死迴圈,原因參加上面++操作說明 /*operator++ 操作是從_M_cur開始,優先_M_cur->_M_next,為空時遍歷vector直至找到一個_M_cur不為空的節點,遍歷vector 時需要取它對應的桶位置(參砍上面hash_map取值過程),_M_bkt_num_key(key)中key的值是修改後的值,假如你改的鍵值,通過 此函式得到的桶位置在你當前元素之前,這樣就造成了死迴圈. */
    }   
    return 0;
}

7.VC下參考例項

#include "stdafx.h"

// 存放過程:key->hash函式->hash值對桶數求模得到桶號(桶有值則解決衝突),存放key和value在桶內
// 取回過程:key->hash函式->hash值對桶數求模得到桶號(桶有值則解決衝突),比較桶內的key是否相等,
// 若不相等則返回空迭代器,否則返回迭代器。

// 1.hash_map為下面型別的key定義了hash定址函式(用於從key到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> 
// 內建的型別直接 hash_map<int, string> mymap;像普通map一樣使用即可。

// 2.自定義hash函式和比較函式
//在宣告自己的雜湊函式時要注意以下幾點:

//使用struct,然後過載operator().
//返回是size_t
//引數是你要hash的key的型別。
//函式是const型別的。 

// 定義自己的比較函式:
//使用struct,然後過載operator().
//返回是bool
//引數是你要hash的key的型別的兩個常量引數,用於比較。
//函式是const型別的。

// 自定義hash函式和比較函式的使用:
// hash_map<ClassA, string, hash_A, equal_A> hmap;

// 3.hash_map使用的常用函式

//hash_map的函式和map的函式差不多。具體函式的引數和解釋,請參看:STL 程式設計手冊:Hash_map,這裡主要介紹幾個常用函式。
//
//hash_map(size_type n) 如果講究效率,這個引數是必須要設定的。n 主要用來設定hash_map 容器中hash桶的個數。
//桶個數越多,hash函式發生衝突的概率就越小,重新申請記憶體的概率就越小。n越大,效率越高,但是記憶體消耗也越大。
//
//const_iterator find(const key_type& k) const. 用查詢,輸入為鍵值,返回為迭代器。
//
//data_type& operator[](const key_type& k) . 這是我最常用的一個函式。因為其特別方便,可像使用陣列一樣使用。
//不過需要注意的是,當你使用[key ]操作符時,如果容器中沒有key元素,這就相當於自動增加了一個key元素。
//因此當你只是想知道容器中是否有key元素時,你可以使用find。如果你希望插入該元素時,你可以直接使用[]操作符。
//
//insert 函式。在容器中不包含key值時,insert函式和[]操作符的功能差不多。但是當容器中元素越來越多,
//每個桶中的元素會增加,為了保證效率,hash_map會自動申請更大的記憶體,以生成更多的桶。因此在insert以後,
//以前的iterator有可能是不可用的。
//
//erase 函式。在insert的過程中,當每個桶的元素太多時,hash_map可能會自動擴充容器的記憶體。
//但在sgi stl中是erase並不自動回收記憶體。因此你呼叫erase後,其他元素的iterator還是可用的。 

#include <hash_map>
#include <string>
#include <iostream>
using namespace std;
using namespace stdext; 
//define the class
/*-------------------------------------------*/  
/*函式類 
 *作為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;}  
};  

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

int main()
{
	// 內建型別
	hash_map<int,string> myHashMap;
	myHashMap[0] = "JesseCen";
	myHashMap[1] = "OZZ";
	hash_map<int,string>::iterator itrHash = myHashMap.find(0);
	if(itrHash != myHashMap.end())
	{
		cout<<"My Name is:"<<itrHash->second.c_str()<<endl;
	}

	// VC下自定義型別
	hash_map<const char*, int, hash_compare<const char*, CharLess> > CharHash;
	CharHash["a"] = 123;
	CharHash["b"] = 456; 
	hash_map<const char*, int, hash_compare<const char*, CharLess> >::iterator itrChar = CharHash.find("b");
	if( itrChar != CharHash.end())
	{
		cout<<"The find number is:"<< itrChar->second<<endl;
	}

	return 0;
}

參考文章:
http://blog.chinaunix.net/uid-20384806-id-3055333.html
http://blog.csdn.net/whizchen/article/details/9286557
http://blog.csdn.net/gamecreating/article/details/7698719
http://www.cppblog.com/peakflys/archive/2012/07/24/184855.aspx