cmap用法,很詳細(轉)


http://hi.baidu.com/wei83523408/blog/item/878ebd3b8898d5e115cecb2b.html

一、 Map的基本知識

  映射(Map),又稱為字典(Dictionary),是由關鍵字(Key)及其對應的元素值(Value)所組成的元素單元 (Element)的表單式集合。

通常,對於Map而言,使用給定的Key,可以迅速地從單元集合中檢索到相應的元素。因此,在需要對大量數據進行查找操作而查找的性能又占據重要地位 的場合,Map無疑是一種較理想的容器。譬如,在MFC中,使用Map來實現HandleMaps(句柄映射),以及其他的一些內部數據結構。同 時,MFC也提供了公共Map類。使用公共Map類,MFC程序員可以輕易地高效地根據自身的需求實現程序中自定義的映射。

通常,當一個Map對象被刪除時,或者,當其中的元素被移除時,關鍵字和元素值也將被完全刪除。

從數據結構的角度分析,有關Map的典型操作有:

1、向Map中插入具有給定關鍵字的元素單元。

2、在Map中查找具有給定關鍵字的元素單元。

3、在Map中刪除具有給定關鍵字的元素單元。

4、枚舉(遍歷)Map中的所有元素單元。

MFC中的各種Map實現,都提供了實現上述操作的成員函數。為了方便討論,我們以CMap為代表,進行講解。

一旦你已經向Map中插入了一個關鍵字-元素值組合對(Key-Value pair)單元,就可以利用關鍵字訪問Map,從而有效地檢索、添加或者刪除元素單元,也可以遍歷Map中的所有單元。

對MFC中的CMap等,除了關鍵字訪問方法之外,還有另一種不同的類型--POSITION,也可以作為訪問元素單元的輔助方式,可以使用一個 POSITION來"記住"一個元素單元或者對Map進行枚舉操作。你可能認為這種使用POSITION實現的遍歷等同於使用關鍵字來進行的Map遍歷, 事實上並非如此,確切的說,兩種檢索的等價性是不確定的。

MFC中的提供了基於模板的CMap類。利用CMap模板類,可以處理特定的數據類型,例如用戶自定義的類或結構體等。同時,MFC也提供了基於指定 數據類型的非模板類,其中包括:

類名 關鍵字類型 元素值類型
CMapWordToPtr WORDS Void pointers
CMapPtrToWord Void pointers WORDS
CMapPtrToPtr Void pointers Void pointers
CMapWordToOb WORDS Objects
CMapStringToOb Strings Objects
CMapStringToPtr Strings Void pointers
CMapStringToString Strings String
二、 Map的工作原理

使用Map的最大優勢是它的快速查找的優秀性能,而取得最優性能的關鍵在於盡量使得在檢索周期內所需進行的元素檢查(比對)次數達到最少。順序查找的 性能是最差的,因為如果使用順序查找算法在包含n個元素單元的Map中查找某個元素,可能(最壞情況下)需要進行n次獨立的比較運算。

二元查找(折中查找)的性能表現要稍好一些,可是,一個不容忽視的問題是--二元查找要求待查序列為有序排列,這無疑會降低Map自身的操作靈活性。 在我們的理解中,所謂的最佳算法應當是不論元素單元數目的多少,也不論元素是以什么順序進行排列,查找過程都無需任何額外的比對操作,而能夠僅僅通過簡單 的計算方法,就可以直接指向最終的相應元素的快速、高效算法。這聽起來有些玄乎,但事實上,這種算法的確是有可能實現的(而且,我相信,Map可以做得 到)。

在MFC的CMap及其相關的Map類中,只要對Map進行正確設置,Lookup函數通常能夠一次到位的查找到任意元素,而很少需要進行兩次或者三 次以上的查找比對。

那么,這種高效的查找是如何實現的呢?

不失一般性,以MFC中的CMap模板類為例。在Map被創建之后(通常是恰恰在第一個元素被插入之前的瞬間),系統會為一個指向CAssoc結構體 的指針數組的哈希表分配內存。MFC使用CAssoc結構體描述元素值和關鍵字的組合對。

CAssoc結構體描述如下:

struct CAssoc
{
CAssoc* pNext;
UINT nHashValue;
CString key;
CString value;
};


無論何時,只要有一個元素值-關鍵字單元被加入到Map中,就會隨之創建一個新的CAssoc結構體,並根據單元中的關鍵字的實際值來計算出相應的哈希 值。同時,拷貝一個指向CAssoc結構體的指針並將其插入到哈希表中索引值為i的位置中。其中,i的計算公式如下:

i=nHashValue%nHushTableSize

式中,nHashValue是由關鍵字Key的實際值計算出來的哈希值;nHashTableSize是哈希表中元素的數目(默認情況下,哈希表的大 小為17)。

如果在哈希表中的索引值為i的位置已經容納了一個CAssoc指針,那么MFC將建立一個單獨的CAssoc結構體的鏈表(List),鏈表中的第一 個CAssoc結構體的地址被存儲到哈希表中,而將第二個CAssoc結構體的地址存儲到前一個CAssoc結構體的pNext域,以此類推。下圖展示了 哈希表的一種可能實現情況,在該哈希表中,共有10個元素,其中5個元素地址分別唯一的存儲,另外5個分別存儲在長度為2和3的兩個鏈表中。

  調用一個Map的Lookup()函數時,MFC根據輸入的關鍵字的實際值計算相應的哈希值,然后使用前面提到的公式將哈希值轉換為索引值,並 從哈希表中的相應位置檢索CAssoc指針。

理想情況下,該位置只包含一個CAssoc指針,而非CAssoc指針鏈表。如果事實情況真如同我們所期望的那樣,單一地址對應單一CAssoc指 針,那么,元素單元將能夠被一次查找到位,並直接讀出;如果從哈希表中檢索到的是CAssoc鏈表的指針頭地址,則MFC順序比對鏈表元素CAssoc結 構所包含的關鍵字,直至查找到正確結果。但是,正如我們先前所討論的那樣,只要正確設置Map,鏈表中的元素一般就不會超過三個,這就意味着,查找通常可 以在三次元素比對操作之內完成。

三、 優化查找效率

  在MFC的Map中,查找性能主要依賴於兩個因素:

1、哈希表的大小

2、盡可能產生唯一哈希值的優異算法

哈希表的大小對於Map的查找性能而言,是非常重要的。舉個簡單的例子,如果有一個Map要容納1000個元素單元,但是哈希表卻只能提供17個存放 CAssoc指針的空間,那么,即使是最佳情況,哈希表中的每個CAssoc鏈表中也將包含58或59個CAssoc結構體,自然,在這種情況下,查找性 能將受到嚴重阻礙。

哈希算法亦是影響查找效率的重要因素之一。如果所使用的哈希算法只能產生少量的不同哈希值(從而也只能產生少量的不同的哈希表索引值),查找性能也同 樣將被降低。

優化Map查找性能的最有效途徑是盡可能的增大哈希表以降低因索引值相同而產生沖突的可能。微軟推薦將哈希表的大小設置為Map中所存儲元素數目的 110% ~120%,以使得Map的應用性能在內存消耗和查找效率之間取得相對平衡。

在MFC中,指定哈希表大小,可調用InitHashTable()函數:

map.InitHashTable(1200);

式中,假設Map中要存儲1000個元素,按照微軟公司的推薦,將哈希表的大小擴展到實際存儲元素數目的120%,即設置Map大小為1200。

從統計學上考慮,實用奇數作為哈希表的大小也將有助於減少沖突的發生。因此,初始化一個存儲1000個元素的哈希表的InitHashTable() 函數可以如下形式使用:

map.InitHashTable(1201);

同時,在InitHashTable()函數的調用時機上,應該注意的是,該函數應當在map包含有任何元素之前使。如果map中已經包含了一個或者 更多的元素,那么,重新改變map的大小,將會引發斷言(Assertion)錯誤。

盡管MFC中所使用的哈希算法能夠適應於大多數場合,但如果您真的有所需要,或者,只要你願意,用戶也可以使用自己的算法來取代原有的算法。對於一個 輸入的關鍵字的值,要計算出它的哈希值,MFC通常調用一個全局模板函數HashKey(),對於大多數數據類型而言,HashKey()函數是以下面的 方式實現的:


AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key)
{
//一般情況的默認算法。
return ((UINT)(void*)(DWORD)key) >> 4;
}

但對於字符串而言,其具體的實現方式如下:

UINT AFXAPI HashKey(LPCWSTR key) // Unicode 編碼字符串
{
UINT nHash = 0;
while (*key)
nHash = (nHash<<5) + nHash + *key++;
return nHash;
}

UINT AFXAPI HashKey(LPCSTR key) // ANSI編碼字符串
{
UINT nHash = 0;
while (*key)
nHash = (nHash<<5) + nHash + *key++;
return nHash;
}

要實現對應於特定數據類型的用戶自定義哈希算法,您可以使用上述的字符串版本的HashKey()函數作為參考,寫一個類似的特定類型的 HashKey()函數。

四、 使用MFC中的CMap類

  有關MFC中的CMap類的概況,上面的文字段落中已經陸續提及,在此不再贅言。下面,列出CMap類的基本成員函數,並通過一個 簡短的程序片段來粗略地演示CMap類的使用方法。

構造函數:


CMap 構造一個關鍵字和元素值映射的集合類。

操作:


Lookup 通過給定的關鍵字查找相應的元素值。
SetAt 向Map中插入一個元素單元;若存在匹配鍵字,則替代之。
operator [] 向Map中插入一個元素 -SetAt的子操作
RemoveKey 移除由關鍵字標示的元素單元
RemoveAll 移除Map中的所有元素單元
GetStartPosition 返回第一個元素單元的位置
GetNextAssoc 讀取下一個元素單元
GetHashTableSize 返回哈希表的大小(元素單元的數目)
InitHashTable 初始化哈希表,並指定它的大小

狀態:

GetCount 返回Map中元素的數目
IsEmpty 檢查Map是否為空(無元素單元)

應用實例如下:

CMap myMap;

//初始化哈希表,並指定其大小(取奇數)。MyMap.InitHashTable(257);

//向myMap中添加元素單元。
for (int i=0;i < 200;i++)
myMap.SetAt( i, CPoint(i, i) );

// 刪除實際值為偶數的關鍵字所對應的的元素單元。
POSITION pos = myMap.GetStartPosition();
int nKey;
CPoint pt;
while (pos != NULL)
{
myMap.GetNextAssoc( pos, nKey, pt );

if ((nKey%2) == 0)
myMap.RemoveKey( nKey );
}

#ifdef _DEBUG
afxDump.SetDepth( 1 );
afxDump << "myMap: " << &myMap << "/n";
#endif

在上面的應用程序片段中,我們可以了解有關CMap類的在通常情況下的使用方法。

1、首先我們使用CMap模板類來定義一個實例--myMap對象。

2、緊接着要做的是對myMap對象的哈希表的大小進行初始化設置。此時,應該先對myMap可能的容量需求進行估計,然后選擇適當大小的奇數-- 或者,有可能的話,使用素數的效果會更好一些--來作為哈希表的初始值。

3、然后,向myMap中添加元素單元。

4、使用myMap進行數據映射、查找、遍歷等操作。

5、調用myMap.RemoveAll()函數移除所有元素,釋放myMap占用的內存空間。

CMap對應IMPLEMENT_SERIAL,從而支持用戶對其元素進行串行化(Serialization)以及傾注(Dumping)操作。在 對CMap的獨立元素進行傾注操作時,應該注意的是,你必須將傾注環境(Dump Context)的深度設置為1或者更大的數字。
注:該文作者E-mail: purestain@sina.com

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com