1. 程式人生 > >C++ STL map的使用和效能分析

C++ STL map的使用和效能分析

1、map簡介

map是一類關聯式容器。它的特點是增加和刪除節點對迭代器的影響很小,除了那個操

作節點,對其他的節點都沒有什麼影響。對於迭代器來說,可以修改實值,而不能修改key。

2、map的功能

自動建立Key - value的對應。key 和 value可以是任意你需要的型別。 根據key值快速查詢記錄,查詢的複雜度基本是Log(N),如果有1000個記錄,最多查詢10次,1,000,000個記錄,最多查詢20次。 快速插入Key - Value 記錄。 快速刪除記錄 根據Key 修改value記錄。 遍歷所有記錄。


3、使用map

使用map得包含map類所在的標頭檔案

#include <map> //注意,STL標頭檔案沒有副檔名.h

map物件是模板類,需要關鍵字和儲存物件兩個模板引數:

std:map<int, string> personnel;

這樣就定義了一個用int作為索引,並擁有相關聯的指向string的指標.

為了使用方便,可以對模板類進行一下型別定義,

typedef map<int, CString> UDT_MAP_INT_CSTRING;

UDT_MAP_INT_CSTRING enumMap;

4、在map中插入元素

改變map中的條目非常簡單,因為map類已經對[]操作符進行了過載

enumMap[1] = "One";

enumMap[2] = "Two";

.....

這樣非常直觀,但存在一個性能的問題。插入2時,先在enumMap中查詢主鍵為2的項,沒

發現,然後將一個新的物件插入enumMap,鍵是2,值是一個空字串,插入完成後,

將字串賦為"Two"; 該方法會將每個值都賦為預設值,然後再賦為顯示的值,如果元素

是類物件,則開銷比較大。我們可以用以下方法來避免開銷:

enumMap.insert(map<int, CString> :: value_type(2, "Two"))

5、查詢並獲取map中的元素

下標操作符給出了獲得一個值的最簡單方法:

CString tmp = enumMap[2];

但是,只有當map中有這個鍵的例項時才對,否則會自動插入一個例項,值為初始化值。

我們可以使用Find()和Count()方法來發現一個鍵是否存在。

查詢map中是否包含某個關鍵字條目用find()方法,傳入的引數是要查詢的key,在這裡

需要提到的是begin()和end()兩個成員,分別代表map物件中第一個條目和最後一個條

目,這兩個資料的型別是iterator.

int nFindKey = 2; //要查詢的Key

//定義一個條目變數(實際是指標)

UDT_MAP_INT_CSTRING::iterator it= enumMap.find(nFindKey);

if(it == enumMap.end()) {

//沒找到

}

else {

//找到

}

通過map物件的方法獲取的iterator資料型別是一個std::pair物件,包括兩個資料

 iterator->first

 和 iterator->second 分別代表關鍵字和儲存的資料

6、從map中刪除元素

移除某個map中某個條目用erase()

該成員方法的定義如下

iterator erase(iterator it); //通過一個條目物件刪除 iterator erase(iterator first, iterator last); //刪除一個範圍 size_type erase(const Key& key); //通過關鍵字刪除
clear()就相當於 enumMap.erase(enumMap.begin(), enumMap.end());

C++ STL map的使用

以下是對C++中STL map的插入,查詢,遍歷及刪除的例子:

#include <map>
#include <string>
#include <iostream>
using namespace std;

void map_insert(map < string, string > *mapStudent, string index, string x)
{
	mapStudent->insert(map < string, string >::value_type(index, x));
}

int main(int argc, char **argv)
{
	char tmp[32] = "";
	map < string, string > mapS;
	
	//insert element
	map_insert(&mapS, "192.168.0.128", "xiong");
	map_insert(&mapS, "192.168.200.3", "feng");
	map_insert(&mapS, "192.168.200.33", "xiongfeng");
	
	map < string, string >::iterator iter;
	
	cout << "We Have Third Element:" << endl;
	cout << "-----------------------------" << endl;
	
	//find element
	iter = mapS.find("192.168.0.33");
	if (iter != mapS.end()) {
		cout << "find the elememt" << endl;
		cout << "It is:" << iter->second << endl;
	} else {
		cout << "not find the element" << endl;
	}
	
	//see element
	for (iter = mapS.begin(); iter != mapS.end(); iter ) {
		
		cout << "| " << iter->first << " | " << iter->
			second << " |" << endl;
		
	}
	cout << "-----------------------------" << endl;
	
	map_insert(&mapS, "192.168.30.23", "xf");
	
	cout << "After We Insert One Element:" << endl;
	cout << "-----------------------------" << endl;
	for (iter = mapS.begin(); iter != mapS.end(); iter ) {
		
		cout << "| " << iter->first << " | " << iter->
			second << " |" << endl;
	}
	
	cout << "-----------------------------" << endl;
	
	//delete element
	iter = mapS.find("192.168.200.33");
	if (iter != mapS.end()) {
		cout << "find the element:" << iter->first << endl;
		cout << "delete element:" << iter->first << endl;
		cout << "=================================" << endl;
		mapS.erase(iter);
	} else {
		cout << "not find the element" << endl;
	}
	for (iter = mapS.begin(); iter != mapS.end(); iter ) {
		
		cout << "| " << iter->first << " | " << iter->
			second << " |" << endl;
		
	}
	cout << "=================================" << endl;
	
	return 0;
} 

map和hash_map效能測試
大家都知道在C++的STL中map是使用樹來做查詢演算法,而hash_map使用hash表來排列配對,是使用關鍵字來計算表位置。那使用起來他們的差別主要是什麼呢?對於效能差別是什麼,適合什麼情況下應用呢?於是我對它們進行了一些測試,並記錄了測試資料供大家分享。
    測試的內容主要是map和hash_map的新增、刪除、查詢和遍歷操作,首先進行了幾組測試,分別是10萬次、30萬次,時間單位均為毫秒,具體的效能對照如下:
 hash_map(10萬) map(10萬) hash_map(20萬) map(20萬) hash_map(30萬) map(30萬)
新增 93   47  156   94  203   172
遍歷 16   15  16   16  16   15
查詢 0   0  32   31  31   32
刪除 8422   32  33765   63  76016   78
   通過上面的資料比較,我們很容易發現hash_map的新增和刪除操作比map要慢,尤其是刪除操作hash_map比map可能慢1000倍;從而得到結論是刪除和插入操作較多的情況下,map比hash_map的效能更好,新增和刪除的資料量越大越明顯。但我們使用map、hash_map一般都用於查詢和遍歷較多,而且上述測試資料也不能反映出這兩方面的效能差距,於是繼續對查詢和遍歷進行了效能測試,得到具體資料如下,時間單位仍為毫秒:
 hash_map(100萬) map(100萬) hash_map(200萬) map(200萬) hash_map(300萬) map(300萬)
遍歷 94   31  203   32  297   47
查詢 94   234  188   531  281   875
   通過上面的測試資料可以得出結論是map的遍歷效能高於hash_map,而查詢效能則相反,hash_map比map要好,資料量越大查詢次數越多,表現就越好。
   兩大組測試完畢,整體結論也可以得出:一般應用情況下,我們儲存的資料不超過100萬份,查詢的頻繁程度不高情況下使用map效能比較好;而儲存的資料較多時(超過100萬),查詢頻繁時使用hash_map的效能就高於map了。

測試環境具體如下:
作業系統:Windows XP Professional (5.1, Build 2600) Service Pack 3(2600.xpsp_sp3_gdr.080814-1236)
編譯環境:Microsoft Visual C++ 2005
 55603-007-4000003-41525
處理器:Intel(R) Core(TM)2 DuoCPU    P8600  @ 2.40GHz (2 CPUs)
記憶體:2044MB RAM,
    另外,整個測試僅使用實體記憶體,而沒有虛擬記憶體,使用Release版本直接在控制檯中執行,而沒有在IDE中執行,避免影響效能;且對於較短時間計時,少於20毫秒以下可能不準確。


詳細解說hash_map
道出map和hash_map的區別

1. STL map is an associative array where keys are stored in sorted order using balanced trees. While hash_map is a hashed associated container, where keys are not stored in an ordered way. Key, value pair is stored using a hashed function.
2. Insertion and lookup takes Ologn time in map, Also performance would degrade as the key size increases. Mainly balance operations on large key ranges would kill performance. while lookup is very efficient O(1) in hash_map.
3. Map is useful where you want to store keys in sorted order, hash_map is used where keys order is not important and lookup is very efficient.
4. One more difference is map has the important property that inserting a new element into a map does not invalidate iterators that point to existing elements. Erasing an element from a map also does not invalidate any iterators.
Performance would mostly be o(lgn) due to the implementation of a balanced tree.
For Map custom objects you would need at the minimum the following operators to store data in a map "<" ">" "==" and of course the other stuff for deep copy.

0 為什麼需要hash_map 1 資料結構:hash_map原理2 hash_map 使用 2.1 一個簡單例項2.2 hash_map 的hash函式2.3 hash_map 的比較函式2.4 hash_map 函式3 相關hash容器4 其他 4.1 hash_map和map的區別在哪裡?4.2 什麼時候需要用hash_map,什麼時候需要用map?4.3 如何在hash_map中加入自己定義的型別?4.4 如何用hash_map替換程式中已有的map容器?4.5 為什麼hash_map不是標準的?4.6 有學習使用hash_map的建議嗎?5 參考文章:
條條大路通羅馬,為什麼你不隨便選一條?

0 為什麼需要hash_map 用過map吧?map提供一個很常用的功能,那就是提供key-value的儲存和查詢功能。例如,我要記錄一個人名和相應的儲存,而且隨時增加,要快速 查詢和修改: 嶽不群-華山派掌門人,人稱君子劍
張三丰-武當掌門人,太極拳創始人
東方不敗-第一高手,葵花寶典
...
這些資訊如果儲存下來並不複雜,但是找起來比較麻煩。例如我要找"張三丰"的資訊,最傻的方法就是取得所有的記錄,然後按照名字一個一個比較。如果要速度快,就需要把這些記錄按照字母順序排列,然後按照二分法查詢。但是增加記錄的時候同時需要保持記錄有序,因此需要插入排序。考慮到效率,這就需要用到二叉樹。講下去會沒完沒了,如果你使用STL 的map容器,你可以非常方便的實現這個功能,而不用關心其細節。關於map的資料結構細節,感興趣的朋友可以參看學習STL map, STL set之資料結構基礎。 看看map的實現:

#include <map>
#include <string>
using namespace std;
...
map<string, string> namemap;

//增加。。。
namemap["嶽不群"]="華山派掌門人,人稱君子劍";
namemap["張三丰"]="武當掌門人,太極拳創始人";
namemap["東方不敗"]="第一高手,葵花寶典";
...

//查詢。。
if(namemap.find("嶽不群") != namemap.end()){
...
}
不覺得用起來很easy嗎?而且效率很高,100萬條記錄,最多也只要20次的string.compare的比較,就能找到你要找的記錄;200萬條記 錄事,也只要用21次的比較。

速度永遠都滿足不了現實的需求。如果有100萬條記錄,我需要頻繁進行搜尋時,20次比較也會成為瓶頸,要是能降到一次或者兩次比較是否有可能?而且當記 錄數到200萬的時候也是一次或者兩次的比較,是否有可能?而且還需要和map一樣的方便使用。

答案是肯定的。這時你需要has_map. 雖然hash_map目前並沒有納入C++ 標準模板庫中,但幾乎每個版本的STL都提供了相應的實現。而且應用十分廣泛。在正式使用hash_map之前,先看看hash_map的原理。

1 資料結構:hash_map原理 這是一節讓你深入理解hash_map的介紹,如果你只是想囫圇吞棗,不想理解其原理,你倒是可以略過這一節,但我還是建議你看看,多瞭解一些沒有壞處。
hash_map基於hash table(雜湊表)。雜湊表最大的優點,就是把資料的儲存和查詢消耗的時間大大降低,幾乎可以看成是常數時間;而代價僅僅是消耗比較多的記憶體。然而在當前可利用記憶體越來越多的情況下,用空間換時間的做法是值得的。另外,編碼比較容易也是它的特點之一。

其基本原理是:使用一個下標範圍比較大的陣列來儲存元素。可以設計一個函式(雜湊函式,也叫做雜湊函式),使得每個元素的關鍵字都與一個函式值(即陣列下標,hash值)相對應,於是用這個陣列單元來儲存這個元素;也可以簡單的理解為,按照關鍵字為每一個元素“分類”,然後將這個元素儲存在相應“類”所對應的地方,稱為桶。

但是,不能夠保證每個元素的關鍵字與函式值是一一對應的,因此極有可能出現對於不同的元素,卻計算出了相同的函式值,這樣就產生了“衝突”,換句話說,就 是把不同的元素分在了相同的“類”之中。 總的來說,“直接定址”與“解決衝突”是雜湊表的兩大特點。

hash_map,首先分配一大片記憶體,形成許多桶。是利用hash函式,對key進行對映到不同區域(桶)進行儲存。其插入過程是:

得到key 通過hash函式得到hash值 得到桶號(一般都為hash值對桶數求模) 存放key和value在桶內。 其取值過程是: 得到key 通過hash函式得到hash值 得到桶號(一般都為hash值對桶數求模) 比較桶的內部元素是否與key相等,若都不相等,則沒有找到。 取出相等的記錄的value。 hash_map中直接地址用hash函式生成,解決衝突,用比較函式解決。這裡可以看出,如果每個桶內部只有一個元素,那麼查詢的時候只有一次比較。當 許多桶內沒有值時,許多查詢就會更快了(指查不到的時候).
由此可見,要實現雜湊表, 和使用者相關的是:hash函式和比較函式。這兩個引數剛好是我們在使用hash_map時需要指定的引數。

2 hash_map 使用 2.1 一個簡單例項 不要著急如何把"嶽不群"用hash_map表示,我們先看一個簡單的例子:隨機給你一個ID號和ID號相應的資訊,ID號的範圍是1~2的31次方。如 何快速儲存查詢。 #include <hash_map>
#include <string>
using namespace std;
int main(){
hash_map<int, string> mymap;
mymap[9527]="唐伯虎點秋香";
mymap[1000000]="百萬富翁的生活";
mymap[10000]="白領的工資底線";
...
if(mymap.find(10000) != mymap.end()){
...
}
夠簡單,和map使用方法一樣。這時你或許會問?hash函式和比較函式呢?不是要指定麼?你說對了,但是在你沒有指定hash函式和比較函式的時候,你 會有一個預設的函式,看看hash_map的宣告,你會更加明白。下面是SGI STL的宣告:

template <class _Key, class _Tp, class _HashFcn = hash<_Key>,
class _EqualKey = equal_to<_Key>,
class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class hash_map
{
...
}
也就是說,在上例中,有以下等同關係:

...
hash_map<int, string> mymap;
//等同於:
hash_map<int, string, hash<int>, equal_to<int> > mymap;
Alloc我們就不要取關注太多了(希望深入瞭解Allocator的朋友可以參看標準庫 STL :Allocator能做什麼)

2.2 hash_map 的hash函式 hash< int>到底是什麼樣子?看看原始碼: struct hash<int> {
size_t operator()(int __x) const { return __x; }
};
原來是個函式物件。在SGI STL中,提供了以下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>
也就是說,如果你的key使用的是以上型別中的一種,你都可以使用預設的hash函式。當然你自己也可以定義自己的hash函式。對於自定義變數,你只能 如此,例如對於string,就必須自定義hash函式。例如:

struct str_hash{
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函式,你可以這樣寫:
struct str_hash{
size_t operator()(const string& str) const
{
return __stl_hash_string(str.c_str());
}
};
在宣告自己的雜湊函式時要注意以下幾點:

使用struct,然後過載operator(). 返回是size_t 引數是你要hash的key的型別。 函式是const型別的。 如果這些比較難記,最簡單的方法就是照貓畫虎,找一個函式改改就是了。
現在可以對開頭的"嶽不群"進行雜湊化了  . 直接替換成下面的宣告即可:

map<string, string> namemap;
//改為:
hash_map<string, string, str_hash> namemap;
其他用法都不用邊。當然不要忘了吧str_hash的宣告以及標頭檔案改為hash_map。

你或許會問:比較函式呢?彆著急,這裡就開始介紹hash_map中的比較函式。

2.3 hash_map 的比較函式 在map中的比較函式,需要提供less函式。如果沒有提供,預設的也是less< Key> 。在hash_map中,要比較桶內的資料和key是否相等,因此需要的是是否等於的函式:equal_to< Key> 。先看看equal_to的原始碼: //本程式碼可以從SGI STL
//先看看binary_function 函式宣告,其實只是定義一些型別而已。
template <class _Arg1, class _Arg2, class _Result>
struct binary_function {
typedef _Arg1 first_argument_type;
typedef _Arg2 second_argument_type;
typedef _Result result_type;
};
//看看equal_to的定義:
template <class _Tp>
struct equal_to : public binary_function<_Tp,_Tp,bool>
{
bool operator()(const _Tp& __x, const _Tp& __y) const { return __x == __y; }
};
如果你使用一個自定義的資料型別,如struct mystruct, 或者const char* 的字串,如何使用比較函式?使用比較函式,有兩種方法. 第一種是:過載==操作符,利用equal_to;看看下面的例子:

struct mystruct{
int iID;
int len;
bool operator==(const mystruct & my) const{
return (iID==my.iID) && (len==my.len) ;
}
};
這樣,就可以使用equal_to< mystruct>作為比較函數了。另一種方法就是使用函式物件。自定義一個比較函式體:

struct compare_str{
bool operator()(const char* p1, const char*p2) const{
return strcmp(p1,p2)==0;
}
};
有了compare_str,就可以使用hash_map了。

typedef hash_map<const char*, string, hash<const char*>, compare_str> StrIntMap;
StrIntMap namemap;
namemap["嶽不群"]="華山派掌門人,人稱君子劍";
namemap["張三丰"]="武當掌門人,太極拳創始人";
namemap["東方不敗"]="第一高手,葵花寶典";2.4 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還是可用的。 3 相關hash容器 hash 容器除了hash_map之外,還有hash_set, hash_multimap, has_multiset, 這些容器使用起來和set, multimap, multiset的區別與hash_map和map的區別一樣,我想不需要我一一細說了吧。 4 其他 這裡列幾個常見問題,應該對你理解和使用hash_map比較有幫助。 4.1 hash_map和map的區別在哪裡? 建構函式。hash_map需要hash函式,等於函式;map只需要比較函式(小於函式). 儲存結構。hash_map採用hash表儲存,map一般採用紅黑樹(RB Tree)實現。因此其memory資料結構是不一樣的。 4.2 什麼時候需要用hash_map,什麼時候需要用map? 總體來說,hash_map 查詢速度會比map快,而且查詢速度基本和資料資料量大小,屬於常數級別;而map的查詢速度是log(n)級別。並不一定常數就比log(n) 小,hash還有hash函式的耗時,明白了吧,如果你考慮效率,特別是在元素達到一定數量級時,考慮考慮hash_map。但若你對記憶體使用特別嚴格,希望程式儘可能少消耗記憶體,那麼一定要小心,hash_map可能會讓你陷入尷尬,特別是當你的hash_map物件特別多時,你就更無法控制了,而且 hash_map的構造速度較慢。
現在知道如何選擇了嗎?權衡三個因素: 查詢速度, 資料量, 記憶體使用。

這裡還有個關於hash_map和map的小故事,看看:http://dev.csdn.net/Develop/article/14/14019.shtm

4.3 如何在hash_map中加入自己定義的型別? 你只要做兩件事, 定義hash函式,定義等於比較函式。下面的程式碼是一個例子: -bash-2.05b$ cat my.cpp
#include <hash_map>
#include <string>
#include <iostream>

using namespace std;
//define the class
class ClassA{
public:
ClassA(int a):c_a(a){}
int getvalue()const { return c_a;}
void setvalue(int a){c_a;}
private:
int c_a;
};

//1 define the hash function
struct hash_A{
size_t operator()(const class ClassA & A)const{
// return hash<int>(classA.getvalue());
return A.getvalue();
}
};

//2 define the equal function
struct equal_A{
bool operator()(const class ClassA & a1, const class ClassA & a2)const{
return a1.getvalue() == a2.getvalue();
}
};

int main()
{
hash_map<ClassA, string, hash_A, equal_A> hmap;
ClassA a1(12);
hmap[a1]="I am 12";
ClassA a2(198877);
hmap[a2]="I am 198877";

cout<<hmap[a1]<<endl;
cout<<hmap[a2]<<endl;
return 0;
}
-bash-2.05b$ make my
c++ -O -pipe -march=pentiumpro my.cpp -o my
-bash-2.05b$ ./my
I am 12
I am 1988774.4如何用hash_map替換程式中已有的map容器? 這個很容易,但需要你有良好的程式設計風格。建議你儘量使用typedef來定義你的型別: typedef map<Key, Value> KeyMap;
當你希望使用hash_map來替換的時候,只需要修改:

typedef hash_map<Key, Value> KeyMap;
其他的基本不變。當然,你需要注意是否有Key型別的hash函式和比較函式。

4.5為什麼hash_map不是標準的? 具體為什麼不是標準的,我也不清楚,有個解釋說在STL加入標準C++之時,hash_map系列當時還沒有完全實現,以後應該會成為標準。如果誰知道更合理的解釋,也希望告訴我。但我想表達的是,正是因為hash_map不是標準的,所以許多平臺上安裝了g++編譯器,不一定有hash_map的實現。我就遇到了這樣的例子。因此在使用這些非標準庫的時候,一定要事先測試。另外,如果考慮到平臺移植,還是少用為佳。 4.6 有學習使用hash_map的建議嗎? hash中文是雜湊,也成為雜湊,聽見別人說雜湊容器不要埋怨自己孤陋寡聞。瞭解hash系列,你還可以看看這篇文章:effective STL 25: 熟悉非標準雜湊容器, 另外建議檢視原始碼。如果還有問題,那麼你可以在STL論壇上提問,會有高手回答你的。